JUnit Comprehensive Cheatsheet¶
Installation¶
| Platform | Installation Method |
|---|---|
| Maven (JUnit 5) | Add to pom.xml: <dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.10.1</version><scope>test</scope></dependency> |
| Maven (JUnit 4) | Add to pom.xml: <dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope></dependency> |
| Gradle (JUnit 5) | Add to build.gradle: testImplementation 'org.junit.jupiter:junit-jupiter:5.10.1' and test { useJUnitPlatform() } |
| Gradle (JUnit 4) | Add to build.gradle: testImplementation 'junit:junit:4.13.2' |
| Ubuntu/Debian | sudo apt update && sudo apt install maven (includes JUnit support) |
| macOS | brew install maven or brew install gradle |
| Windows (Chocolatey) | choco install maven or choco install gradle |
| Standalone JAR | wget https://repo1.maven.org/maven2/org/junit/jupiter/junit-jupiter/5.10.1/junit-jupiter-5.10.1.jar |
Core Annotations (JUnit 5)¶
| Annotation | Description |
|---|---|
@Test |
Marks a method as a test method |
@BeforeEach |
Executes before each test method in the class |
@AfterEach |
Executes after each test method in the class |
@BeforeAll |
Executes once before all tests (must be static) |
@AfterAll |
Executes once after all tests (must be static) |
@DisplayName("name") |
Provides custom display name for test class or method |
@Disabled |
Disables a test class or method |
@Disabled("reason") |
Disables with explanation message |
@Nested |
Denotes a nested test class for organizing tests |
@Tag("tagname") |
Tags tests for filtering (e.g., "slow", "integration") |
@RepeatedTest(n) |
Repeats test execution n times |
@TestFactory |
Marks a method as a dynamic test factory |
@TestInstance(Lifecycle.PER_CLASS) |
Changes test instance lifecycle |
@Timeout(value = 5) |
Fails test if execution exceeds 5 seconds |
@Order(n) |
Specifies test execution order when using @TestMethodOrder |
Basic Assertions¶
| Assertion | Description |
|---|---|
assertEquals(expected, actual) |
Asserts that two values are equal |
assertEquals(expected, actual, "msg") |
Asserts equality with custom failure message |
assertEquals(expected, actual, delta) |
Asserts floating-point equality within delta |
assertNotEquals(unexpected, actual) |
Asserts that two values are not equal |
assertTrue(condition) |
Asserts that condition is true |
assertFalse(condition) |
Asserts that condition is false |
assertNull(object) |
Asserts that object is null |
assertNotNull(object) |
Asserts that object is not null |
assertSame(expected, actual) |
Asserts that two references point to same object |
assertNotSame(obj1, obj2) |
Asserts that two references point to different objects |
assertArrayEquals(expected, actual) |
Asserts that two arrays are equal |
assertIterableEquals(expected, actual) |
Asserts that two iterables are equal |
assertLinesMatch(expected, actual) |
Asserts that two lists of strings match (supports regex) |
fail("message") |
Explicitly fails a test with message |
assertAll("group", () -> {...}) |
Groups multiple assertions (all execute even if some fail) |
Advanced Assertions¶
| Assertion | Description |
|---|---|
assertThrows(Exception.class, () -> {...}) |
Asserts that executable throws specified exception |
assertDoesNotThrow(() -> {...}) |
Asserts that executable does not throw any exception |
assertTimeout(Duration.ofSeconds(2), () -> {...}) |
Asserts that execution completes within timeout |
assertTimeoutPreemptively(Duration, () -> {...}) |
Asserts timeout and aborts execution if exceeded |
assertInstanceOf(Class.class, object) |
Asserts that object is instance of specified class |
assertAll(() -> {...}, () -> {...}) |
Executes all assertions and reports all failures |
Parameterized Test Annotations¶
| Annotation | Description |
|---|---|
@ParameterizedTest |
Marks method as parameterized test |
@ValueSource(ints = {1, 2, 3}) |
Provides array of literal values as parameters |
@ValueSource(strings = {"a", "b"}) |
Provides array of string values |
@CsvSource({"1,2,3", "4,5,9"}) |
Provides CSV data as parameters |
@CsvFileSource(resources = "/data.csv") |
Loads parameters from CSV file |
@MethodSource("methodName") |
Uses method return value as parameter source |
@EnumSource(MyEnum.class) |
Uses enum values as parameters |
@NullSource |
Provides null as parameter |
@EmptySource |
Provides empty value (String, Collection, Array) |
@NullAndEmptySource |
Combines @NullSource and @EmptySource |
@ArgumentsSource(Provider.class) |
Uses custom ArgumentsProvider implementation |
Maven Commands¶
| Command | Description |
|---|---|
mvn test |
Runs all tests in the project |
mvn test -Dtest=ClassName |
Runs specific test class |
mvn test -Dtest=ClassName#methodName |
Runs specific test method |
mvn test -Dtest=*Test |
Runs all classes matching pattern |
mvn test -Dtest=Class1,Class2 |
Runs multiple specific test classes |
mvn clean test |
Cleans previous builds and runs tests |
mvn test -DskipTests |
Skips test execution |
mvn test -Dmaven.test.skip=true |
Skips test compilation and execution |
mvn test -Dmaven.surefire.debug |
Runs tests in debug mode (port 5005) |
mvn test -Dgroups=integration |
Runs only tests tagged with "integration" |
mvn test -DexcludedGroups=slow |
Excludes tests tagged with "slow" |
mvn surefire-report:report |
Generates HTML test report |
mvn test jacoco:report |
Runs tests and generates code coverage report |
mvn verify |
Runs tests and integration tests |
mvn test -Dsurefire.rerunFailingTestsCount=2 |
Reruns failing tests up to 2 times |
Gradle Commands¶
| Command | Description |
|---|---|
gradle test |
Runs all tests |
./gradlew test |
Runs tests using Gradle wrapper |
gradle test --tests ClassName |
Runs specific test class |
gradle test --tests ClassName.methodName |
Runs specific test method |
gradle test --tests *Test |
Runs all classes matching pattern |
gradle clean test |
Cleans and runs tests |
gradle test --continuous |
Runs tests continuously on file changes |
gradle test --info |
Runs tests with detailed output |
gradle test --debug |
Runs tests with debug logging |
gradle test --rerun-tasks |
Forces test rerun even if up-to-date |
gradle test --fail-fast |
Stops execution after first test failure |
gradle test --tests *Test --parallel |
Runs tests in parallel |
gradle test jacocoTestReport |
Runs tests and generates coverage report |
Configuration¶
Maven Surefire Plugin Configuration¶
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<!-- Include/exclude test patterns -->
<includes>
<include>**/*Test.java</include>
<include>**/*Tests.java</include>
</includes>
<excludes>
<exclude>**/*IntegrationTest.java</exclude>
</excludes>
<!-- Parallel execution -->
<parallel>methods</parallel>
<threadCount>4</threadCount>
<!-- Tag filtering -->
<groups>unit, integration</groups>
<excludedGroups>slow</excludedGroups>
<!-- System properties -->
<systemPropertyVariables>
<env>test</env>
</systemPropertyVariables>
</configuration>
</plugin>
Gradle Test Configuration¶
test {
useJUnitPlatform {
// Include/exclude tags
includeTags 'unit', 'integration'
excludeTags 'slow'
// Include/exclude engines
includeEngines 'junit-jupiter'
excludeEngines 'junit-vintage'
}
// Parallel execution
maxParallelForks = 4
// Test filtering
filter {
includeTestsMatching '*Test'
excludeTestsMatching '*IntegrationTest'
}
// System properties
systemProperty 'env', 'test'
// Test logging
testLogging {
events "passed", "skipped", "failed"
exceptionFormat "full"
showStandardStreams = true
}
// Fail fast
failFast = true
}
JUnit Platform Properties (junit-platform.properties)¶
# Parallel execution
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent
junit.jupiter.execution.parallel.config.strategy = fixed
junit.jupiter.execution.parallel.config.fixed.parallelism = 4
# Test instance lifecycle
junit.jupiter.testinstance.lifecycle.default = per_class
# Display name generation
junit.jupiter.displayname.generator.default = org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores
# Automatic extension detection
junit.jupiter.extensions.autodetection.enabled = true
# Deactivate conditions
junit.jupiter.conditions.deactivate = org.junit.*DisabledCondition
Common Use Cases¶
Use Case: Basic Unit Test with Setup and Teardown¶
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
class UserServiceTest {
private UserService userService;
private Database mockDb;
@BeforeEach
void setUp() {
mockDb = new MockDatabase();
userService = new UserService(mockDb);
}
@Test
@DisplayName("Should create user with valid data")
void testCreateUser() {
User user = new User("john@example.com", "password123");
User created = userService.createUser(user);
assertNotNull(created.getId());
assertEquals("john@example.com", created.getEmail());
}
@AfterEach
void tearDown() {
mockDb.close();
}
}
Use Case: Parameterized Testing for Multiple Inputs¶
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.*;
class CalculatorTest {
@ParameterizedTest
@CsvSource({
"1, 1, 2",
"2, 3, 5",
"10, -5, 5",
"0, 0, 0"
})
void testAddition(int a, int b, int expected) {
Calculator calc = new Calculator();
assertEquals(expected, calc.add(a, b));
}
@ParameterizedTest
@ValueSource(strings = {"", " ", "\t", "\n"})
void testBlankStrings(String input) {
assertTrue(StringUtils.isBlank(input));
}
@ParameterizedTest
@MethodSource("provideEmailTestCases")
void testEmailValidation(String email, boolean expected) {
assertEquals(expected, EmailValidator.isValid(email));
}
static Stream<Arguments> provideEmailTestCases() {
return Stream.of(
Arguments.of("test@example.com", true),
Arguments.of("invalid.email", false),
Arguments.of("@example.com", false)
);
}
}
Use Case: Exception Testing and Timeout Verification¶
import org.junit.jupiter.api.Test;
import java.time.Duration;
import static org.junit.jupiter.api.Assertions.*;
class PaymentServiceTest {
@Test
void shouldThrowExceptionForInvalidAmount() {
PaymentService service = new PaymentService();
Exception exception = assertThrows(
IllegalArgumentException.class,
() -> service.processPayment(-100)
);
assertEquals("Amount must be positive", exception.getMessage());
}
@Test
void shouldCompleteWithinTimeout() {
PaymentService service = new PaymentService();
assertTimeout(Duration.ofSeconds(2), () -> {
service.processPayment(100);
});
}
@Test
void shouldNotThrowExceptionForValidPayment() {
PaymentService service = new PaymentService();
assertDoesNotThrow(() -> {
service.processPayment(100);
});
}
}
Use Case: Nested Tests for Organized Test Structure¶
import org.junit.jupiter.api.*;
@DisplayName("Shopping Cart Tests")
class ShoppingCartTest {
@Nested
@DisplayName("When cart is empty")
class EmptyCart {
private ShoppingCart cart;
@BeforeEach
void setUp() {
cart = new ShoppingCart();
}
@Test
@DisplayName("Should have zero items")
void testItemCount() {
assertEquals(0, cart.getItemCount());
}
@Test
@DisplayName("Should have zero total")
void testTotal() {
assertEquals(0.0, cart.getTotal());
}
}
@Nested
@DisplayName("When cart has items")
class CartWithItems {
private ShoppingCart cart;
@BeforeEach
void setUp() {
cart = new ShoppingCart();
cart.addItem(new Item("Book", 29.99));
cart.addItem(new Item("Pen", 2.50));
}
@Test
@DisplayName("Should calculate correct total")
void testTotal() {
assertEquals(32.49, cart.getTotal(), 0.01);
}
@Test
@DisplayName("Should allow item removal")
void testRemoveItem() {
cart.removeItem(0);
assertEquals(1, cart.getItemCount());
}
}
}
Use Case: Integration Testing with Test Lifecycle Management¶
import org.junit.jupiter.api.*;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class DatabaseIntegrationTest {
private static DatabaseConnection connection;
private UserRepository userRepository;
@BeforeAll
void setUpDatabase() {
// Expensive one-time setup
connection = DatabaseConnection.connect("jdbc:h2:mem:test");
connection.runMigrations();
}
@BeforeEach
void setUp() {
userRepository = new UserRepository(connection);
connection.beginTransaction();
}
@Test
@Tag("integration")
void testUserPersistence() {
User user = new User("test@example.com");
userRepository.save(user);
User retrieved = userRepository.findById(user.getId());
assertEquals(user.getEmail(), retrieved.getEmail());
}
@AfterEach
void tearDown() {
connection.rollbackTransaction();
}
@AfterAll
void tearDownDatabase() {
connection.close();
}
}
Best Practices¶
-
Use descriptive test names: Leverage
@DisplayNameto make test purposes clear. Test names should describe what is being tested and the expected outcome (e.g., "Should throw exception when amount is negative"). -
Follow AAA pattern: Structure tests with Arrange (setup), Act (execute), and Assert (verify) sections for clarity and maintainability.
-
Test one thing per test: Each test method should verify a single behavior or scenario. This makes failures easier to diagnose and tests easier to maintain.
-
Use @BeforeEach and @AfterEach wisely: Initialize common test fixtures in
@BeforeEachbut avoid excessive setup that makes tests hard to understand. Clean up resources in@AfterEach. -
Prefer @ParameterizedTest for multiple similar cases: Instead of writing multiple similar test methods, use parameterized tests to test the same logic with different inputs.
-
Use assertAll for related assertions: Group related assertions with
assertAll()to see all failures at once rather than stopping at the first failure. -
Tag tests appropriately: Use
@Tagto categorize tests (unit, integration, slow) so you can selectively run test suites in different environments or CI/CD stages. -
Avoid test interdependencies: Tests should be independent and able to run in any order. Never rely on execution order or state from other tests.
-
Use meaningful assertion messages: Provide custom failure messages to assertions to make debugging easier:
assertEquals(expected, actual, "User email should match"). -
Keep tests fast: Unit tests should execute quickly. Move slow tests (database, network) to separate integration test suites using tags or separate source sets.
Troubleshooting¶
| Issue | Solution |
|---|---|
| Tests not discovered/running | Ensure test methods are public or package-private, annotated with @Test, and class names match pattern *Test or *Tests. For Maven, verify surefire plugin version ≥ 2.22.0 for JUnit 5 support. |
| NoClassDefFoundError for JUnit classes | Add JUnit dependency with <scope>test</scope> in Maven or testImplementation in Gradle. Verify correct JUnit version (5.x for Jupiter, 4.x for legacy). |
| @BeforeAll/@AfterAll must be static error | Methods with @BeforeAll/@AfterAll must be static unless using @TestInstance(Lifecycle.PER_CLASS) on the test class. |
| Parameterized tests not running | Add junit-jupiter-params dependency: org.junit.jupiter:junit-jupiter-params:5.10.1. Ensure method has @ParameterizedTest instead of @Test. |
| Tests pass in IDE but fail in Maven/Gradle | Check for different JUnit versions between IDE and build tool. Verify maven-surefire-plugin version ≥ 2.22.0 or Gradle has useJUnitPlatform() in test configuration. |
| Assertion methods not found | Import static assertion methods: import static org.junit.jupiter.api.Assertions.*; for JUnit 5 or import static org.junit.Assert.*; for JUnit 4. |
| Tests run but results not displayed | For Maven, check surefire reports in target/surefire-reports/. For Gradle, check build/reports/tests/test/index.html or add testLogging configuration. |
| Timeout tests failing unexpectedly | Use assertTimeoutPreemptively() instead of assertTimeout() if test code doesn't respect interruption. Check for blocking I/O or synchronization issues. |
| Cannot mix JUnit 4 and JUnit 5 annotations | Use JUnit Vintage engine to run JUnit 4 tests alongside JUnit 5, or migrate all tests to JUnit 5. Add junit-vintage-engine dependency for backward compatibility. |
| Gradle tests always UP-TO-DATE | Use gradle cleanTest test or gradle test --rerun-tasks to force test execution. Check if test source files are in correct directory (src/test/java). |