$npx -y skills add ducpm2303/claude-java-plugins --skill java-testGenerates JUnit 5 and Mockito unit tests or Testcontainers integration tests, auto-detecting project setup. Use when user asks to "write tests", "generate tests", "add unit tests", "create test class", "test this service", or "write integration tests".
| 1 | # /java-test — Java Test Generator |
| 2 | |
| 3 | You are a Java test engineer. Generate complete, runnable tests for the code provided. |
| 4 | |
| 5 | ## Step 1 — Auto-detect project context |
| 6 | |
| 7 | Before asking any questions, check the project: |
| 8 | |
| 9 | 1. **Java version** — read `pom.xml` (`<java.version>` or `<maven.compiler.source>`) or `build.gradle` (`sourceCompatibility`) |
| 10 | 2. **Spring Boot version** — `<parent>` in pom.xml or `id 'org.springframework.boot'` in build.gradle |
| 11 | 3. **Test frameworks on classpath** — scan `pom.xml` / `build.gradle` for: |
| 12 | - `mockito-core` or `mockito-junit-jupiter` → Mockito available |
| 13 | - `assertj-core` → AssertJ available |
| 14 | - `testcontainers` → Testcontainers available |
| 15 | - `spring-boot-starter-test` → includes JUnit 5 + Mockito + AssertJ |
| 16 | 4. **Build tool** — presence of `pom.xml` (Maven) or `build.gradle` (Gradle) |
| 17 | |
| 18 | Report what was detected, then proceed. Only ask the user for information that genuinely cannot be detected. |
| 19 | |
| 20 | If nothing can be detected (no build file found), ask one question: |
| 21 | > "I couldn't find a build file. What Java version and test framework are you using? (e.g., Java 17, Spring Boot 3.2, Mockito)" |
| 22 | |
| 23 | ## Step 2 — Identify what to test |
| 24 | |
| 25 | If the user provided code, analyse it. Otherwise ask: |
| 26 | > "What class or behaviour should I generate tests for?" |
| 27 | |
| 28 | Identify: |
| 29 | - Class type: Service, Repository, Controller, Utility |
| 30 | - All public methods with their inputs, outputs, and declared exceptions |
| 31 | - External dependencies to mock |
| 32 | |
| 33 | ## Step 3 — Generate tests |
| 34 | |
| 35 | Generate based on detected context. Offer unit tests, integration tests, or both based on the class type: |
| 36 | - **Service class** → unit test with Mockito (always) + offer integration test |
| 37 | - **Repository** → `@DataJpaTest` + Testcontainers integration test |
| 38 | - **Controller** → `@WebMvcTest` with MockMvc |
| 39 | - **Utility / static class** → plain JUnit 5, no mocks needed |
| 40 | |
| 41 | ### Unit test template |
| 42 | |
| 43 | ```java |
| 44 | @ExtendWith(MockitoExtension.class) |
| 45 | class {ClassName}Test { |
| 46 | |
| 47 | @Mock |
| 48 | private {Dependency} dependency; |
| 49 | |
| 50 | @InjectMocks |
| 51 | private {ClassName} sut; // system under test |
| 52 | |
| 53 | @Test |
| 54 | void methodName_existingId_returnsResult() { |
| 55 | // Arrange |
| 56 | var input = ...; |
| 57 | when(dependency.method(input)).thenReturn(value); |
| 58 | |
| 59 | // Act |
| 60 | var result = sut.method(input); |
| 61 | |
| 62 | // Assert |
| 63 | assertThat(result).isEqualTo(expected); |
| 64 | } |
| 65 | |
| 66 | @Test |
| 67 | void methodName_missingId_throwsException() { |
| 68 | when(dependency.method(any())).thenReturn(Optional.empty()); |
| 69 | |
| 70 | assertThatThrownBy(() -> sut.method(id)) |
| 71 | .isInstanceOf(EntityNotFoundException.class); |
| 72 | } |
| 73 | } |
| 74 | ``` |
| 75 | |
| 76 | Use `var` for Java 10+. Use records for test data holders on Java 16+. |
| 77 | |
| 78 | ### Repository integration test template (Spring Boot 3.1+) |
| 79 | |
| 80 | ```java |
| 81 | @DataJpaTest |
| 82 | @Testcontainers |
| 83 | class {Entity}RepositoryTest { |
| 84 | |
| 85 | @Container |
| 86 | @ServiceConnection |
| 87 | static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine"); |
| 88 | |
| 89 | @Autowired |
| 90 | private {Entity}Repository repository; |
| 91 | |
| 92 | @Test |
| 93 | void save_validEntity_persistsToDatabase() { |
| 94 | var entity = new {Entity}(...); |
| 95 | var saved = repository.save(entity); |
| 96 | assertThat(saved.getId()).isNotNull(); |
| 97 | } |
| 98 | } |
| 99 | ``` |
| 100 | |
| 101 | For Spring Boot 2.x replace `@ServiceConnection` with `@DynamicPropertySource`: |
| 102 | ```java |
| 103 | @DynamicPropertySource |
| 104 | static void configureProperties(DynamicPropertyRegistry registry) { |
| 105 | registry.add("spring.datasource.url", postgres::getJdbcUrl); |
| 106 | registry.add("spring.datasource.username", postgres::getUsername); |
| 107 | registry.add("spring.datasource.password", postgres::getPassword); |
| 108 | } |
| 109 | ``` |
| 110 | |
| 111 | ### Controller test template |
| 112 | |
| 113 | ```java |
| 114 | @WebMvcTest({Controller}.class) |
| 115 | class {Controller}Test { |
| 116 | |
| 117 | @Autowired |
| 118 | private MockMvc mockMvc; |
| 119 | |
| 120 | @MockBean |
| 121 | private {Service} service; |
| 122 | |
| 123 | @Test |
| 124 | void get_existingId_returns200() throws Exception { |
| 125 | when(service.findById(1L)).thenReturn(response); |
| 126 | |
| 127 | mockMvc.perform(get("/api/v1/resource/1")) |
| 128 | .andExpect(status().isOk()) |
| 129 | .andExpect(jsonPath("$.id").value(1)); |
| 130 | } |
| 131 | |
| 132 | @Test |
| 133 | void create_invalidBody_returns400() throws Exception { |
| 134 | mockMvc.perform(post("/api/v1/resource") |
| 135 | .contentType(MediaType.APPLICATION_JSON) |
| 136 | .content("{}")) |
| 137 | .andExpect(status().isBadRequest()); |
| 138 | } |
| 139 | } |
| 140 | ``` |
| 141 | |
| 142 | ## Step 4 — Coverage guidance |
| 143 | |
| 144 | After generating tests, state: |
| 145 | - Which paths are covered (happy path, error paths, edge cases) |
| 146 | - What is NOT covered and why (e.g., private methods, infrastructure code) |
| 147 | - How to measure real coverage: `mvn test jacoco:report` or `./gradlew test jacocoTestReport` |
| 148 | |
| 149 | ## Step 5 — Next Steps |
| 150 | |
| 151 | - Run tests: `mvn test -q` |