Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use when writing tests of any kind — unit, slice, or integration. Covers test structure, naming conventions, Mockito patterns, @WebMvcTest, @DataJpaTest, and Testcontainers setup.
.claude/skills/rrezartprebreza-testing-pyramid/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 27% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 28% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 45% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 61% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 70% | 0% |
Unit Tests — fast, no Spring context, mock dependencies (70%)
Slice Tests — partial Spring context (@WebMvcTest, @DataJpaTest) (20%)
Integration Tests — full context + real DB via Testcontainers (10%)java@ExtendWith(MockitoExtension.class) class OrderServiceTest { @Mock private OrderRepository orderRepository; @Mock private InventoryService inventoryService; @InjectMocks private OrderService orderService; @Test void createOrder_whenItemsAvailable_shouldSaveAndReturnOrder() { // Given var request = new CreateOrderRequest("user@example.com", List.of(new OrderItemRequest(UUID.randomUUID(), 2))); var savedOrder = Order.create("user@example.com"); when(orderRepository.save(any(Order.class))).thenReturn(savedOrder); doNothing().when(inventoryService).reserve(any()); // When Order result = orderService.createOrder(request); // Then assertThat(result).isNotNull(); assertThat(result.getCustomerEmail()).isEqualTo("user@example.com"); verify(inventoryService).reserve(request.items()); verify(orderRepository).save(any(Order.class)); } @Test void createOrder_whenInventoryUnavailable_shouldThrowException() { // Given var request = new CreateOrderRequest("user@example.com", List.of()); doThrow(new InsufficientInventoryException("Out of stock")) .when(inventoryService).reserve(any()); // When / Then assertThatThrownBy(() -> orderService.createOrder(request)) .isInstanceOf(InsufficientInventoryException.class) .hasMessage("Out of stock"); } }
java@WebMvcTest(OrderController.class) class OrderControllerTest { @Autowired MockMvc mockMvc; @Autowired ObjectMapper objectMapper; @MockitoBean OrderService orderService; @Test @WithMockUser(roles = "USER") void createOrder_withValidRequest_shouldReturn201() throws Exception { // Given var request = new CreateOrderRequest("user@example.com", List.of()); var order = Order.create("user@example.com"); when(orderService.createOrder(any())).thenReturn(order); // When / Then mockMvc.perform(post("/api/v1/orders") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isCreated()) .andExpect(jsonPath("$.success").value(true)) .andExpect(jsonPath("$.data.customerEmail").value("user@example.com")); } @Test @WithMockUser void createOrder_withInvalidRequest_shouldReturn400() throws Exception { mockMvc.perform(post("/api/v1/orders") .contentType(MediaType.APPLICATION_JSON) .content("{}")) // missing required fields .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.success").value(false)) .andExpect(jsonPath("$.error.code").value("VALIDATION_FAILED")); } }
java@DataJpaTest @AutoConfigureTestDatabase(replace = Replace.NONE) // use real DB (Testcontainers) @Import(TestcontainersConfig.class) class OrderRepositoryTest { @Autowired OrderRepository orderRepository; @Test void findByStatus_shouldReturnMatchingOrders() { // Given var order1 = orderRepository.save(Order.create("a@example.com")); var order2 = orderRepository.save(Order.create("b@example.com")); order2.ship(); // change status orderRepository.save(order2); // When List<Order> pending = orderRepository.findByStatus(OrderStatus.PENDING, Pageable.unpaged()).getContent(); // Then assertThat(pending).hasSize(1); assertThat(pending.get(0).getCustomerEmail()).isEqualTo("a@example.com"); } }
java// Shared config — reuse container across tests @TestConfiguration(proxyBeanMethods = false) public class TestcontainersConfig { @Bean @ServiceConnection PostgreSQLContainer<?> postgresContainer() { return new PostgreSQLContainer<>("postgres:16-alpine"); } } // Full integration test @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @AutoConfigureRestTestClient @Import(TestcontainersConfig.class) class OrderIntegrationTest { @Autowired RestTestClient restTestClient; @Autowired OrderRepository orderRepository; @Test void createAndRetrieveOrder_endToEnd() { // Create var createRequest = new CreateOrderRequest("user@example.com", List.of()); restTestClient.post() .uri("/api/v1/orders") .body(createRequest) .exchange() .expectStatus().isCreated() .expectBody(ApiResponse.class); // Retrieve // ... assert persisted correctly } }
// Method name: methodName_condition_expectedBehavior
createOrder_whenItemsAvailable_shouldSaveOrder()
findById_whenOrderNotFound_shouldThrowNotFoundException()
login_withInvalidCredentials_shouldReturn401()xml<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-testcontainers</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>postgresql</artifactId> <scope>test</scope> </dependency>
@SpringBootTest for everything — use slices for speedH2 in-memory DB for @DataJpaTest — use Testcontainers for accuracy@MockBean — removed in Boot 4; use @MockitoBean (and @MockitoSpyBean for spies)TestRestTemplate by habit — prefer RestTestClient with @AutoConfigureRestTestClient for Boot 4 HTTP integration testsMockito.mock() instead of @Mock — use annotations with @ExtendWith(MockitoExtension.class)@WithMockUser on controller tests — security filter blocks all requestsassertEquals from JUnit — use AssertJ (assertThat(...).isEqualTo(...))test_createOrder() — use createOrder_condition_expected() pattern| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | pass→pass | 21,755 | 17,751 | -18% | 1 | 1 | 0% | 3,025 | 3,718 | +23% | 0 | 0 | — |
case-02 | fail→pass | 24,308 | 15,361 | -37% | 1 | 1 | 0% | 3,160 | 4,017 | +27% | 0 | 0 | — |
case-03 | fail→pass | 16,686 | 16,823 | +1% | 1 | 1 | 0% | 3,384 | 4,341 | +28% | 0 | 0 | — |
case-16 | fail→pass | 11,798 | 9,746 | -17% | 1 | 1 | 0% | 2,158 | 3,131 | +45% | 0 | 0 | — |
case-04 | pass→pass | 11,335 | 7,501 | -34% | 1 | 1 | 0% | 1,274 | 2,868 | +125% | 0 | 0 | — |
case-05 | pass→pass | 11,363 | 13,232 | +16% | 1 | 1 | 0% | 1,189 | 2,851 | +140% | 0 | 0 | — |
case-06 | pass→pass | 15,268 | 8,667 | -43% | 1 | 1 | 0% | 2,157 | 2,901 | +34% | 0 | 0 | — |
case-07 | fail→pass | 25,060 | 21,071 | -16% | 1 | 1 | 0% | 2,818 | 4,528 | +61% | 0 | 0 | — |
case-17 | pass→pass | 15,343 | 12,900 | -16% | 1 | 1 | 0% | 2,708 | 3,800 | +40% | 0 | 0 | — |
case-08 | pass→pass | 7,687 | 10,580 | +38% | 1 | 1 | 0% | 1,486 | 2,600 | +75% | 0 | 0 | — |
case-09 | pass→pass | 10,292 | 9,437 | -8% | 1 | 1 | 0% | 941 | 2,290 | +143% | 0 | 0 | — |
case-10 | fail→pass | 10,771 | 5,675 | -47% | 1 | 1 | 0% | 1,552 | 2,644 | +70% | 0 | 0 | — |
case-11 | pass→pass | 11,958 | 9,676 | -19% | 1 | 1 | 0% | 2,261 | 3,275 | +45% | 0 | 0 | — |
case-18 | pass→pass | 10,792 | 9,579 | -11% | 1 | 1 | 0% | 2,275 | 3,432 | +51% | 0 | 0 | — |
case-12 | pass→pass | 18,408 | 10,744 | -42% | 1 | 1 | 0% | 2,538 | 3,541 | +40% | 0 | 0 | — |
case-13 | pass→pass | 12,988 | 15,045 | +16% | 1 | 1 | 0% | 2,540 | 4,012 | +58% | 0 | 0 | — |
case-14 | pass→pass | 9,877 | 4,992 | -49% | 1 | 1 | 0% | 1,538 | 2,642 | +72% | 0 | 0 | — |
case-15 | pass→pass | 5,487 | 4,975 | -9% | 1 | 1 | 0% | 998 | 2,575 | +158% | 0 | 0 | — |
case-19 | pass→pass | 9,349 | 5,064 | -46% | 1 | 1 | 0% | 1,590 | 2,578 | +62% | 0 | 0 | — |
case-20 | pass→pass | 18,384 | 9,135 | -50% | 1 | 1 | 0% | 2,987 | 3,495 | +17% | 0 | 0 | — |
case-21 | fail→pass | 15,025 | 8,918 | -41% | 1 | 1 | 0% | 2,388 | 3,186 | +33% | 0 | 0 | — |
case-22 | fail→pass | 8,020 | 4,686 | -42% | 1 | 1 | 0% | 1,589 | 2,546 | +60% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +32 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.