JUnit 5


1️. What is JUnit?

JUnit is a testing framework for Java used to write and run automated tests.

JUnit 5 is the modern version and consists of three main components:

  • JUnit Platform → Launches testing frameworks
  • JUnit Jupiter → Programming model & API (most commonly used)
  • JUnit Vintage → Supports JUnit 3 & 4 tests

Most backend projects use:

org.junit.jupiter.*

2️. JUnit 5 Architecture

JUnit 5 = Platform + Jupiter + Vintage

In Spring Boot projects, spring-boot-starter-test automatically includes JUnit Jupiter.

3️. Basic Test Structure

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class CalculatorTest {

    @Test
    void add_shouldReturnSum() {
        int result = 2 + 3;
        assertEquals(5, result);
    }
}

Important Notes

  • Test class does NOT need to be public.
  • Test methods do NOT need to be public.
  • No main() method required.
  • @Test marks executable test methods.

4️. Core Annotations

Annotation Purpose
@Test Marks test method
@DisplayName Custom readable name
@BeforeEach Runs before every test
@AfterEach Runs after every test
@BeforeAll Runs once before all tests
@AfterAll Runs once after all tests
@Disabled Skips a test
@Nested Groups related tests

5️. Test Lifecycle

5.1 @BeforeEach / @AfterEach

Executed before/after every test method.

@BeforeEach
void setup() {
    System.out.println("Runs before each test");
}

@AfterEach
void cleanup() {
    System.out.println("Runs after each test");
}

5.2 @BeforeAll / @AfterAll

  • Executed once per test class.
  • ⚠ Must be static by default.
@BeforeAll
static void initAll() {
    System.out.println("Runs once before all tests");
}

5.3 Test Instance Lifecycle

By default, JUnit creates a new instance of the test class per test method.

Default:

PER_METHOD

Alternative:

@TestInstance(TestInstance.Lifecycle.PER_CLASS)

This allows non-static @BeforeAll methods.


6️. Assertions

Assertions validate expected outcomes.

Import:

import static org.junit.jupiter.api.Assertions.*;

Common Assertions

assertEquals(expected, actual);
assertNotEquals(unexpected, actual);
assertTrue(condition);
assertFalse(condition);
assertNull(object);
assertNotNull(object);

6.1 assertThrows

Used to verify exception behavior.

assertThrows(IllegalArgumentException.class, () -> {
service.process(null);
});

6.2 assertAll

Executes multiple assertions together.

assertAll(
() -> assertEquals(5, result),
() -> assertTrue(result > 0)
);

All assertions execute even if one fails

6.3 assertTimeout

Fails if execution exceeds time limit.

import java.time.Duration;

assertTimeout(Duration.ofSeconds(2), () -> {
service.longRunningMethod();
});


7. Assumptions

Assumptions skip tests if conditions are not met.

import static org.junit.jupiter.api.Assumptions.*;

Assumptions works like this:

  • 👉 When the assumption fails, JUnit throws a special exception
  • 👉 The test is marked SKIPPED (aborted)
  • 👉 Execution stops immediately at that line

Example :

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assumptions.*;

class AssumeFlowExample {

    @Test
    void testFlow() {

        System.out.println("1 - First statement");
        System.out.println("2 - Second statement");

        assumeTrue(false, "Condition failed");   // 3rd statement

        System.out.println("4 - Fourth statement");
        System.out.println("5 - Fifth statement");
    }
}

What happens?

Execution order:

1 - First statement   ✅ executed
2 - Second statement  ✅ executed
assumeTrue(false)     ❌ fails here
4 - Fourth statement  ❌ NOT executed
5 - Fifth statement   ❌ NOT executed

Final Result:

  • Test status → Skipped
  • Statements before assumption → Executed
  • Statements after assumption → Not executed

Assumptions behave like:

if (!condition) {
throw TestAbortedException;
}

Note ::

  • 👉 Put assumeTrue() at the top of the test
  • So you avoid partially executing test logic.

assumeTrue()

👉 Test runs only if condition is true
👉 If false → test is skipped, not failed

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assumptions.*;

class AssumeTrueExample {

    @Test
    void testOnlyOnDevEnvironment() {

        String env = System.getProperty("ENV");

        assumeTrue("DEV".equals(env), "Test runs only in DEV environment");

        System.out.println("Running test in DEV environment");
    }
}
ENV value Result
DEV Test runs
PROD Test skipped
null Test skipped

assumeFalse()

👉 Test runs only if condition is false
👉 If true → test is skipped

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assumptions.*;

class AssumeFalseExample {

    @Test
    void testNotForWindows() {

        String os = System.getProperty("os.name");

        assumeFalse(os.contains("Windows"), "Test skipped on Windows");

        System.out.println("Running test on non-Windows OS");
    }
}
OS Result
Windows Test skipped
Linux / Mac Test runs

assumingThat()

👉 Conditional execution inside test
👉 If condition false → block skipped
👉 But rest of test still executes

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assumptions.*;
import static org.junit.jupiter.api.Assertions.*;

class AssumingThatExample {

    @Test
    void testWithOptionalBlock() {

        String env = System.getProperty("ENV");

        assumingThat("DEV".equals(env), () -> {
            System.out.println("Running DEV specific assertions");
            assertEquals(2, 1 + 1);
        });

        // This always runs
        assertTrue(true);
        System.out.println("Common test logic executed");
    }
}
ENV DEV block Rest of test
DEV Executed Executed
PROD Skipped Executed

Method If Condition Fails Entire Test Skipped?
assumeTrue Yes Yes
assumeFalse Yes Yes
assumingThat Only block skipped No

8️. Parameterized Tests

Used to run same test with multiple inputs.

8.1 @ValueSource


@ParameterizedTest
@ValueSource(ints = {2, 4, 6, 8})
void shouldBeEven(int number) {
    assertTrue(number % 2 == 0);
}

8.2 @CsvSource

@ParameterizedTest
@CsvSource({
"apple, 5",
"banana, 6"
})
void checkLength(String fruit, int length) {
assertEquals(length, fruit.length());
}

8.3 @CsvFileSource

@ParameterizedTest
@CsvFileSource(resources = "/data.csv", delimiter = ';')
void testFromFile(String input, int expected) {
    assertEquals(expected, input.length());
}

9️. Repeated Tests

@RepeatedTest(5)
void repeatTest() {
    assertTrue(true);
}

Runs same test multiple times.


10. Nested Tests

Used to group related test scenarios.

@Nested
class ValidInputTests {

    @Test
    void shouldReturnSuccess() {
        assertTrue(true);
    }
}

Improves readability.


11. Test Ordering

⚠ Not recommended unless necessary.

Using @Order

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class OrderedTests {

    @Test
    @Order(1)
    void first() {}

    @Test
    @Order(2)
    void second() {}
}

Alphanumeric Order

Executes test methods in alphanumeric order based on their method names.

@TestMethodOrder(MethodOrderer.Alphanumeric.class)
class OrderedTests {

    @Test
    void first() {}

    @Test
    void second() {}
}

12️. Conditional Test Execution

Run tests only in specific environments.

Examples:

@EnabledOnOs(OS.WINDOWS)
@DisabledOnJre(JRE.JAVA_17)
@EnabledIfSystemProperty(named = "env", matches = "test")
@Disabled

13. Parallel Execution

JUnit 5 supports parallel execution.

Enable via junit-platform.properties:

junit.jupiter.execution.parallel.enabled=true

Use carefully:

  • Tests must be independent
  • No shared mutable state

14️. Display Names & Reporting

@DisplayName(“Should calculate interest correctly”)

Improves readability in reports.


15. Maven Configuration

JUnit runs automatically via: maven-surefire-plugin

To generate reports:

mvn surefire-report:report

Report location:

target/site/surefire-report.html


Best Practices

  • Use meaningful test names
  • Follow Arrange–Act–Assert
  • One behavior per test
  • Avoid testing implementation details
  • Keep tests independent
  • Do not rely on execution order
  • Prefer parameterized tests for multiple inputs

This site uses Just the Docs, a documentation theme for Jekyll.