From java-services
Enforces Java 21 + Spring Boot 3.x test standards using JUnit 5, Mockito, AssertJ, and Testcontainers for src/test/java/ only (NOT src/main/java/). Use when writing Java tests, generating test files, or implementing test helpers/mocks under src/test/java/.
npx claudepluginhub andercore-labs/claudes-kitchen --plugin java-servicesThis skill uses the workspace's default tool permissions.
**SCOPE:** Test code under `src/test/java/` only. Production code has separate standards.
Implements structured self-debugging workflow for AI agent failures: capture errors, diagnose patterns like loops or context overflow, apply contained recoveries, and generate introspection reports.
Monitors deployed URLs for regressions in HTTP status, console errors, performance metrics, content, network, and APIs after deploys, merges, or upgrades.
Provides React and Next.js patterns for component composition, compound components, state management, data fetching, performance optimization, forms, routing, and accessible UIs.
SCOPE: Test code under src/test/java/ only. Production code has separate standards.
Unit test (JUnit 5 + Mockito + AssertJ):
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock private OrderRepository repository;
@InjectMocks private OrderService service;
@Test
void shouldReturnSavedOrder_whenValidOrderIsPlaced() {
// Given
var order = OrderMother.valid();
when(repository.save(any(Order.class))).thenReturn(order);
// When
var result = service.place(order);
// Then
assertThat(result).isEqualTo(order);
verify(repository, times(1)).save(order);
}
}
Testcontainers integration test:
@DataJpaTest
@Testcontainers
class OrderRepositoryIT {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");
@DynamicPropertySource
static void props(DynamicPropertyRegistry r) {
r.add("spring.datasource.url", postgres::getJdbcUrl);
r.add("spring.datasource.username", postgres::getUsername);
r.add("spring.datasource.password", postgres::getPassword);
}
@Autowired private OrderRepository repository;
@Test
void given_savedOrder_when_findById_then_returnsOrder() {
var saved = repository.save(OrderMother.valid());
assertThat(repository.findById(saved.getId())).isPresent().hasValue(saved);
}
}
Unit tests | Integration tests | Testcontainers | JUnit 5 | Mockito | AssertJ
src/test/java/
├── unit/ → pure unit tests, no Spring context
├── integration/ → Spring slice tests (@WebMvcTest, @DataJpaTest)
└── e2e/ → @SpringBootTest + real containers
| Type | Annotation | Loads | Speed |
|---|---|---|---|
| Unit | @ExtendWith(MockitoExtension.class) | Nothing | <50ms |
| Integration | @WebMvcTest, @DataJpaTest, @JsonTest | Slice | Fast |
| E2E | @SpringBootTest | Full context | Slower |
| Annotation | Loads | Use for |
|---|---|---|
@WebMvcTest(XxxController.class) | MVC layer only | Controller tests |
@DataJpaTest | JPA + H2/real DB | Repository tests |
@JsonTest | Jackson only | Serialization tests |
@SpringBootTest | Full context | E2E / smoke |
| Pattern | Status |
|---|---|
@Test + @DisplayName("given X when Y then Z") | ✓ |
@ParameterizedTest + @MethodSource / @CsvSource / @EnumSource | ✓ |
@BeforeEach / @AfterEach for setup/teardown | ✓ |
@Nested for grouping related cases | ✓ |
@ExtendWith(MockitoExtension.class) in unit tests | ✓ |
@Before / @After (JUnit 4) | ✗ |
PER_CLASS lifecycle only for expensive setup | ✓ conditional |
PER_METHOD default lifecycle | ✓ default |
Parameterized:
@ParameterizedTest
@CsvSource({"PENDING,false", "PAID,true", "CANCELLED,false"})
void given_status_when_isPaid_then_returnsExpected(OrderStatus status, boolean expected) {
assertThat(OrderMother.withStatus(status).isPaid()).isEqualTo(expected);
}
@ParameterizedTest
@MethodSource("invalidEmails")
void given_invalidEmail_when_creating_then_throws(String email) {
assertThatThrownBy(() -> new Customer(email))
.isInstanceOf(ValidationException.class)
.hasMessageContaining("email");
}
static Stream<String> invalidEmails() {
return Stream.of("", "notanemail", "missing@");
}
| Pattern | Status |
|---|---|
@Mock for dependencies | ✓ |
@InjectMocks for subject under test | ✓ |
@Captor for argument capture | ✓ |
when(mock.method(arg)).thenReturn(value) | ✓ |
verify(mock, times(1)).method(arg) | ✓ |
any(Foo.class) typed matchers | ✓ |
doReturn (reserve for void/spy) | ✓ conditional |
@MockBean in unit tests | ✗ CRITICAL |
Raw any() without type | ✗ |
Missing verify() after interaction | ✗ |
@Captor ArgumentCaptor<OrderEvent> eventCaptor;
@Test
void given_placedOrder_when_confirmed_then_publishesEvent() {
service.confirm(ORDER_ID);
verify(eventPublisher, times(1)).publish(eventCaptor.capture());
assertThat(eventCaptor.getValue().getOrderId()).isEqualTo(ORDER_ID);
}
| Pattern | Status |
|---|---|
assertThat(actual).isEqualTo(expected) | ✓ |
assertThat(list).containsExactlyInAnyOrder(...) | ✓ |
assertThat(optional).isPresent().hasValue(x) | ✓ |
assertThatThrownBy(() -> ...).isInstanceOf(X.class).hasMessage(...) | ✓ |
assertThat(result).extracting("field1","field2").containsExactly(v1,v2) | ✓ |
assertTrue / assertEquals / assertNull (JUnit) | ✗ CRITICAL |
assertTrue(list.contains(...)) | ✗ CRITICAL |
assertThat(orders)
.hasSize(2)
.extracting(Order::getStatus)
.containsExactlyInAnyOrder(PAID, PENDING);
assertThatThrownBy(() -> service.cancel(nonExistentId))
.isInstanceOf(OrderNotFoundException.class)
.hasMessage("Order not found: " + nonExistentId);
public final class OrderMother {
private OrderMother() {}
public static Order valid() {
return Order.builder().id(UUID.randomUUID()).status(PENDING).build();
}
public static Order withStatus(OrderStatus status) {
return valid().toBuilder().status(status).build();
}
public static Order paid() { return withStatus(PAID); }
}
| Pattern | Status |
|---|---|
Object Mother static factories (XxxMother.valid()) | ✓ |
| Builder pattern for complex entities | ✓ |
| Named constants / factory methods | ✓ |
| Magic literals | ✗ |
| Shared mutable test state between tests | ✗ CRITICAL |
| Infrastructure | Container | Forbidden Alternative |
|---|---|---|
| PostgreSQL | PostgreSQLContainer<?> | H2 in-memory for JPA behaviour |
| Kafka | KafkaContainer | Embedded Kafka for real produce/consume |
| Redis | GenericContainer("redis:7") | Embedded Redis |
| S3 / Kinesis / SQS (AWS) | LocalStackContainer | Mocked AWS SDK clients |
@Testcontainers
class KafkaConsumerIT {
@Container
static KafkaContainer kafka = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7"));
@DynamicPropertySource
static void kafkaProps(DynamicPropertyRegistry r) {
r.add("spring.kafka.bootstrap-servers", kafka::getBootstrapServers);
}
}
| Element | Convention |
|---|---|
| Unit test class | XxxTest |
| Integration test class | XxxIT (runs with Failsafe) |
| Test method | shouldReturn<X>_when<Condition> (Andercore standard) |
| Test file location | mirrors production package |
| GWT body structure | // Given / // When / // Then comments — mandatory |
// ✓ Andercore naming + mandatory GWT comments
@Test
void shouldThrowNotFoundException_whenOrderIdDoesNotExist() {
// Given
var missingId = UUID.randomUUID();
// When / Then
assertThatThrownBy(() -> service.findById(missingId))
.isInstanceOf(OrderNotFoundException.class);
}
| Forbidden | Use instead |
|---|---|
test1(), testSuccess(), checkIt() | shouldReturnX_whenConditionY |
test_it() (JUnit 4 style) | camelCase with should prefix |
Missing // Given / // When / // Then comments | Always include GWT structure |
| Never Allowed | Use Instead |
|---|---|
Thread.sleep() in tests | Awaitility await().until(...) |
System.out.println() | Assertions |
@Autowired in unit tests | @InjectMocks |
| Testing private methods directly | Test through public API |
@SpringBootTest for unit tests | @ExtendWith(MockitoExtension.class) |
| Mocking value objects / records | Construct directly |
@MockBean in unit tests | @Mock + @InjectMocks |
| H2 for integration JPA tests | @Testcontainers + real DB |
| Shared mutable state across tests | @BeforeEach clean state |
@Before / @After (JUnit 4) | @BeforeEach / @AfterEach |
assertEquals, assertTrue | AssertJ assertThat(...) |
CHECK:
1. File location → src/test/java/unit|integration|e2e mirrors production package
2. Test class naming → XxxTest (unit) | XxxIT (integration)
3. Method naming → `shouldReturn<X>_when<Condition>` format
3a. GWT comments → `// Given` / `// When` / `// Then` present in every test body
4. @ExtendWith(MockitoExtension.class) → unit tests only (no Spring)
5. @Mock / @InjectMocks → no @MockBean / @Autowired in unit tests
6. Mockito → when().thenReturn() not doReturn() unless void
7. verify() → every mock interaction verified with typed args
8. AssertJ → assertThat() exclusively; no assertEquals/assertTrue/assertNull
9. Optional → assertThat(opt).isPresent().hasValue(x)
10. Exceptions → assertThatThrownBy().isInstanceOf().hasMessage()
11. Testcontainers → real DB/Kafka/Redis for integration/e2e; no H2 for JPA behaviour
12. @DynamicPropertySource → container URLs injected into Spring context
13. Object Mother → factory methods; no magic literals; no shared mutable state
14. @ParameterizedTest → @MethodSource / @CsvSource / @EnumSource (no raw loops)
15. @Nested → related cases grouped; @BeforeEach for shared setup within group
16. Thread.sleep() → ABSENT; use Awaitility
17. @SpringBootTest → e2e/ ONLY; never in unit/
18. Private methods → tested through public API only
19. All pass → execute tests
ANY fail → REJECT with violation
| Phase | Action |
|---|---|
| 1. SCOPE | Extract: files, mode (informative | executive), sessionId |
| 2. VERIFY | Run ALL 19 checks on test code |
| 3. VIOLATIONS | Collect: file:line, check name, severity |
| 4. REPORT | ✓ ALL pass → Proceed | ✗ ANY fail → REJECT with violations |
| 5. METRICS | Call mcp__agent-orchestrator__store-skill-metrics |
Metrics structure:
{
"sessionId": "string",
"skill": "java-services:test-code-recipe",
"initialViolations": 0,
"iterations": 1,
"fixesApplied": 0,
"finalViolations": 0,
"mode": "informative | executive",
"duration": 0.0
}
Output format:
{
"status": "success | failed",
"violations": [
{
"file": "src/test/java/unit/OrderServiceTest.java",
"line": 22,
"check": "AssertJ",
"violation": "assertEquals used instead of assertThat",
"severity": "critical"
}
],
"metrics": {
"initialViolations": 1,
"finalViolations": 0,
"fixRate": 1.0
}
}