java-junit
Get best practices for JUnit 5 unit testing, including data-driven tests
By github · 11,346 installs
npx skills add github/awesome-copilot --skill java-junit
Source repository · Upstream listing
Java Testing with JUnit 5/6, AssertJ and Mockito
Framework neutral guidance for fast, deterministic unit tests. For Spring slices use spring boot testing (if installed); for real databases/brokers in Docker use java testcontainers ; for failures you cannot explain, java debugging .
First: detect, don't impose
1. Read pom.xml / build.gradle : JUnit version ( junit jupiter , junit bom , or inherited from a Spring Boot BOM), assertion library, Mockito, Surefire/Failsafe versions, Java release.
2. Copy the conventions already in src/test/java (naming, assertion library, static imports, mocking style). Do not introduce AssertJ, Mockito or JaCoCo into a project that lacks them without asking.
3. Never bump the JUnit major version implicitly; upgrading is its own task (see java upgrade if installed).
JUnit 5 vs JUnit 6
JUnit 5 (last 5.14.x) JUnit 6 (6.0 released; 6.1.x current)
Java baseline 8 17 (verified in the published Gradle module metadata)
Versioning Platform 1.x, Jupiter 5.x, Vintage 5.x One version number for Platform, Jupiter and Vintage
API org.junit.jupiter.api. Same packages; the Jupiter tests in this skill compile and pass unchanged on 5.14.4 and 6.0.3
Removed in 6.0 MethodOrderer.Alphanumeric , the deprecated two argument InvocationInterceptor.interceptDynamicTest(Invocation, ExtensionContext) overload (the three argument one remains), junit.jupiter.tempdir.scope , @CsvFileSource(lineSeparator)
CSV univocity parsers FastCSV: stricter parsing (no characters after a closing quote); header field options now apply to headers
Other junit jupiter migrationsupport deprecated; invalid enum style config values now fail discovery; @Nested class order deterministic but non obvious
Source: JUnit 6.0.0 release notes (<https://docs.junit.org/6.0.0/release notes.html ). Read the notes for the target version (<https://docs.junit.org/current/release notes/ ) and prefer them over this table. JUnit 4 tests need the Vintage engine; do not mix org.junit.Test and org.junit.jupiter.api.Test in one class.
Dependencies (same for 5 and 6; verified with Maven and Gradle): import org.junit:junit bom , then declare org.junit.jupiter:junit jupiter (aggregates api, params, engine) with test scope. Gradle 9 additionally requires testRuntimeOnly("org.junit.platform:junit platform launcher") (verified: without it the test executor fails with "Failed to load JUnit Platform") and tasks.test { useJUnitPlatform() } . Maven needs Surefire 3.x (verified with 3.6.0).
Assertions
Match the project's library. Plain Jupiter rules: expected value first, lazy Supplier messages, assertAll , assertThrowsExactly , timeouts. (The separate instructions/java junit5 assertions.instructions.md resource has the longer version but is not installed with this skill.) If the project uses AssertJ, prefer it for collections, strings and exceptions; patterns (soft assertions, assertThatThrownBy , extracting , containsExactly , usingRecursiveComparison ) are in references/assertj and mockito.md .
Assert on outcomes and state, not on how many times internals were called.
Compare values; avoid bare assertNotNull .
Never catch AssertionError / Exception to make a test pass.
Mocking (Mockito)
Mock collaborators across a boundary (repository, HTTP client, clock, message sender). Do not mock value objects, records, collections, String , or the class under test; use real instances or simple fakes.
Prefer a hand written fake over a mock when the interaction is stateful.
Use @ExtendWith(MockitoExtension.class) with @Mock / @InjectMocks / @Captor . Strict stubs are the default there, so an unused stub fails the test ( UnnecessaryStubbingException ). Fix the test; relax with @MockitoSettings(strictness = Strictness.LENIENT) only for a justified shared setup.
Mockito 5 uses the inline mock maker by default. On JDK 21+ it may self attach and print a dynamic agent warning. With Mockito 5.14.0+ , register mockito core as a javaagent instead (setup for Maven and Gradle in references/assertj and mockito.md , verified on JDK 21 and 25). Check the resolved version first: earlier 5.x jars lack Premain Class and cannot be used as the agent. Ask before upgrading; otherwise use the instrumentation setup documented for the installed version.
Mockito 5 requires Java 11+. Its default inline mock maker does not work on GraalVM native image, where the subclass mock maker is appropriate (Mockito 5.0.0 release notes, <https://github.com/mockito/mockito/releases/tag/v5.0.0 ).
Avoid mockStatic / mockConstruction ; wrap the static call behind an interface.
Determinism
Time: inject java.time.Clock ( Clock.fixed(...) ) and call Instant.now(clock) ; avoid Instant.now() / LocalDate.now() in code under test.
Randomness: inject a Random / RandomGenerator with a fixed seed; log the seed if generated.
Do not depend on default locale, time zone, file encoding, network, or wall clock sleeps. Set them explicitly (newer 6.x releases add built in locale/time zone/system property extensions; check the release notes of your version, otherwise use try/finally).
Unit vs integration tests
Unit ( Test ) Integration ( IT )
Maven Surefire, mvn test Failsafe, mvn verify
Gradle test task separate integrationTest task (JVM Test Suite) wired into check
Speed milliseconds, no I/O may use Docker/DB
Surefire defaults pick Test , Test , Tests , TestCase ; Failsafe picks IT , IT , ITCase . Failsafe needs the integration test and verify goals bound and is run by mvn verify , not mvn test . Alternatively split by @Tag . For databases and brokers see java testcontainers ; for Spring slices see spring boot testing . Full build snippets: references/build setup.md .
Coverage and mutation testing (optional)
JaCoCo: add only if the project uses it or the user asks. With Surefire/Failsafe use <argLine @{argLine} ...</argLine so the JaCoCo agent is not overwritten.
Coverage is a smell detector, not a goal; never add assertion free tests to raise a number, and do not add a coverage gate the project lacks.
PIT (mutation testing) is slow; run it on changed classes only if the project already uses it or the user asks (see references/build setup.md , which records a verified pitest maven + JUnit 5 plugin run).
Agent guardrails
1. Never delete, comment out, @Disabled , weaken (looser assertion, wider tolerance, swallowed exception) or skip a failing test to get green. Find the cause.
2. Decide which side is wrong, the test or the production code. If the fix changes production behavior , stop and ask the user first; if the test encodes an obviously stale expectation, say so and propose the change.
3. Do not add DskipTests , Dmaven.test.skip , x test or failIfNoTests=false to make a build pass.
4. Do not mock the class under test or assert on mock internals to "prove" correctness.
5. Run the narrowest test first, then the full build:
Maven: mvn q Dtest=OrdersTest test (one method: Dtest='OrdersTest place should saveAndNotify when itemValid' ), then mvn verify .
Gradle: ./gradlew test tests 'demo.OrdersTest' , then ./gradlew check .
Prefer the project wrapper ( ./mvnw , ./gradlew ) when present.
6. Flaky test: reproduce by rerunning, then remove the nondeterminism (time, order, shared state, async waits); do not add retries.
7. New code gets new tests beside the existing ones, in the same style: happy path, boundaries, error cases.
References
references/assertj and mockito.md : AssertJ patterns, Mockito strictness/captors/agent setup, when not to mock.
references/build setup.md : Maven and Gradle (Kotlin DSL) setups for JUnit 5/6, Failsafe split, JaCoCo, PIT.