From backend-java
Use when writing tests for Java code — JUnit 5 with AssertJ assertions, Mockito mocking, Testcontainers for integration tests, and Spring Boot test slices
How this skill is triggered — by the user, by Claude, or both
Slash command
/backend-java:java-testingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
```java
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock OrderRepository orderRepository;
@Mock EventPublisher eventPublisher;
@InjectMocks OrderService orderService;
@Test
void createOrder_withValidRequest_returnsOrderResponse() {
// Arrange
var request = new CreateOrderRequest("customer-1", List.of(new ItemRequest("p1", 2)));
when(orderRepository.save(any(Order.class))).thenAnswer(inv -> {
var order = inv.getArgument(0, Order.class);
ReflectionTestUtils.setField(order, "id", 1L);
return order;
});
// Act
var result = orderService.create(request);
// Assert
assertThat(result.id()).isEqualTo(1L);
assertThat(result.customerId()).isEqualTo("customer-1");
verify(eventPublisher).publish(any(OrderCreatedEvent.class));
}
}
@SpringBootTest
@Testcontainers
class OrderRepositoryIT {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
@Autowired OrderRepository orderRepository;
@Test
void save_persistsOrder() {
var order = Order.create("c1", List.of());
orderRepository.save(order);
assertThat(orderRepository.findById(order.getId())).isPresent();
}
}
methodName_condition_expectedResultcreateOrder_withEmptyItems_throwsValidationExceptionassertThat) — never JUnit's assertEqualsassertThatThrownBy for exception testingStructure test classes with clearly named methods covering all four categories.
@Test
void createOrder_withValidRequest_returnsOrderResponse() {
var request = new CreateOrderRequest("customer-1", List.of(new ItemRequest("p1", 2)));
when(orderRepository.save(any())).thenAnswer(inv -> {
var order = inv.getArgument(0, Order.class);
ReflectionTestUtils.setField(order, "id", 1L);
return order;
});
var result = orderService.create(request);
assertThat(result.id()).isEqualTo(1L);
verify(eventPublisher).publish(any(OrderCreatedEvent.class));
}
@Test
void createOrder_withEmptyItems_throwsValidationException() {
var request = new CreateOrderRequest("customer-1", List.of());
assertThatThrownBy(() -> orderService.create(request))
.isInstanceOf(ValidationException.class)
.hasMessageContaining("items cannot be empty");
}
@Test
void getOrder_whenNotFound_throwsNotFoundException() {
when(orderRepository.findById(anyLong())).thenReturn(Optional.empty());
assertThatThrownBy(() -> orderService.getById(999L))
.isInstanceOf(OrderNotFoundException.class);
}
@ParameterizedTest
@ValueSource(ints = {0, -1, Integer.MAX_VALUE})
void createOrder_withBoundaryQuantity_handlesCorrectly(int quantity) {
var request = new CreateOrderRequest("c1", List.of(new ItemRequest("p1", quantity)));
if (quantity <= 0) {
assertThatThrownBy(() -> orderService.create(request))
.isInstanceOf(ValidationException.class);
} else {
var result = orderService.create(request);
assertThat(result).isNotNull();
}
}
@Test
void createOrder_withMaxLengthCustomerId_succeeds() {
var longId = "c".repeat(255);
var request = new CreateOrderRequest(longId, List.of(new ItemRequest("p1", 1)));
var result = orderService.create(request);
assertThat(result.customerId()).hasSize(255);
}
@ParameterizedTest
@ValueSource(strings = {"'; DROP TABLE orders; --", "<script>alert(1)</script>", "../../../etc/passwd"})
void createOrder_withMaliciousInput_rejectsOrSanitizes(String maliciousInput) {
var request = new CreateOrderRequest(maliciousInput, List.of(new ItemRequest("p1", 1)));
assertThatThrownBy(() -> orderService.create(request))
.isInstanceOf(ValidationException.class)
.hasMessageNotContaining("SQL")
.hasMessageNotContaining("Exception");
}
@Test
void getOrder_withoutAuthentication_returns401() throws Exception {
mockMvc.perform(get("/api/v1/orders/1"))
.andExpect(status().isUnauthorized())
.andExpect(content().string(not(containsString("stack trace"))))
.andExpect(content().string(not(containsString("jdbc:"))));
}
@Test
void createOrder_withExtraFields_ignoresMassAssignment() throws Exception {
var json = """
{"customerId":"c1","items":[{"productId":"p1","quantity":1}],"isAdmin":true,"discount":100}
""";
mockMvc.perform(post("/api/v1/orders")
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.discount").value(0));
}
npx claudepluginhub gagandeepp/software-agent-teams --plugin backend-javaGuides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Enforces test-driven development: write failing test first, then minimal code to pass. Use when implementing features or bugfixes.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.