From qa-unit-tests-jvm
Configures and runs JUnit 5 (Jupiter) - modern JVM testing platform with annotations (`@Test` / `@ParameterizedTest` / `@RepeatedTest` / `@TestFactory`), lifecycle hooks (`@BeforeAll` / `@BeforeEach` / `@AfterEach` / `@AfterAll`), extension model (`@ExtendWith`), display names (`@DisplayName`), conditional execution (`@EnabledOnOs`, `@EnabledIf`), parallel execution config; integrates with Maven Surefire / Gradle test task / IntelliJ. Use when the user works with Java / Kotlin codebases needing the modern JVM standard.
How this skill is triggered — by the user, by Claude, or both
Slash command
/qa-unit-tests-jvm:junit5-testsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Per [junit.org/junit5/docs/current/user-guide][j5-ug]:
Per junit.org/junit5/docs/current/user-guide:
Three components (released 2017, replacing JUnit 4):
This skill targets JUnit Jupiter (the modern API). For Kotlin-native
tests with similar power but DSL-style, see kotest-tests.
Maven pom.xml:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
Gradle build.gradle.kts:
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter:5.11.0")
}
tasks.test {
useJUnitPlatform()
}
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
@Test
void addsTwoNumbers() {
assertEquals(3, Calculator.add(1, 2));
}
}
Run: mvn test or ./gradlew test.
Per j5-ug:
class UserServiceTest {
@BeforeAll
static void initAll() { /* once before all */ }
@AfterAll
static void tearDownAll() { /* once after all */ }
@BeforeEach
void init() { /* before each test */ }
@AfterEach
void tearDown() { /* after each test */ }
@Test
void test1() { ... }
@Test
void test2() { ... }
}
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.*;
class ParametrizedTest {
@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 5, 8})
void numbersInFibonacci(int n) {
assertTrue(isFibonacci(n));
}
@ParameterizedTest
@CsvSource({
"1, 2, 3",
"0, 0, 0",
"-1, 1, 0",
})
void addCases(int a, int b, int expected) {
assertEquals(expected, Calculator.add(a, b));
}
@ParameterizedTest
@MethodSource("addProvider")
void addsViaMethodSource(int a, int b, int expected) {
assertEquals(expected, Calculator.add(a, b));
}
static Stream<Arguments> addProvider() {
return Stream.of(
Arguments.of(1, 2, 3),
Arguments.of(0, 0, 0)
);
}
}
Source providers: @ValueSource, @CsvSource, @CsvFileSource,
@MethodSource, @EnumSource, @ArgumentsSource.
@ExtendWith)JUnit 5's extension model (replaces JUnit 4's @Rule /
@RunWith):
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository repo;
@InjectMocks
private UserService service;
@Test
void createsUser() {
when(repo.save(any())).thenReturn(new User(1, "Alice"));
User u = service.create("Alice");
assertEquals(1, u.getId());
}
}
Common extensions: MockitoExtension, SpringExtension (Spring),
SystemStubsExtension (env vars / system properties),
TempDirectory.
@DisplayName("User service")
class UserServiceTest {
@Test
@DisplayName("creates a user with email lowercased")
void createsUserWithLowercaseEmail() { ... }
@Test
@EnabledOnOs(OS.LINUX)
void linuxOnlyTest() { ... }
@Test
@EnabledIfEnvironmentVariable(named = "INTEGRATION", matches = "true")
void integrationOnly() { ... }
@Test
@Disabled("Re-enable after fixing JIRA-1234")
void temporarilyDisabled() { ... }
}
Enable in junit-platform.properties:
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent
junit.jupiter.execution.parallel.config.strategy = dynamic
Per-test override:
@Execution(ExecutionMode.SAME_THREAD)
class TestNotParallelizable { ... }
Maven:
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<executions>
<execution>
<goals><goal>prepare-agent</goal></goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals><goal>report</goal></goals>
</execution>
<execution>
<id>jacoco-check</id>
<goals><goal>check</goal></goals>
<configuration>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.80</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
- run: ./gradlew test jacocoTestReport
- uses: codecov/codecov-action@v4
with: { files: ./build/reports/jacoco/test/jacocoTestReport.xml }
Surefire (Maven) emits JUnit XML for junit-xml-analysis.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Mix JUnit 4 + JUnit 5 in same project | Two runners, confusing | Pick Jupiter; use Vintage only for migration |
@Test from org.junit.Test (JUnit 4) | Doesn't run with Jupiter runner | Import org.junit.jupiter.api.Test (Step 2) |
| Skip parallel-execution config | Slow test suite at scale | Enable parallel.enabled (Step 7) |
Use @Disabled without ticket reference | Forgotten disabled tests | Always include reason + JIRA link (Step 6) |
Generic assertTrue(x.equals(y)) | Loses diff in failure | assertEquals(x, y) |
kotest-tests,
spock-tests,
testng-tests,
scalatest - sister toolstest-code-conventions - test code hygienenpx claudepluginhub testland/qa --plugin qa-unit-tests-jvmProvides CDSS development patterns for drug interaction checking, dose validation, clinical scoring (NEWS2, qSOFA), and alert classification integrated into EMR workflows.