← Back to Java series
πŸ”§
Modern Java
Modern Java Β· Prerequisite: previous lecture

22. JUnit 5 Unit Testing

Saying "the code works" requires **verification**. JUnit 5 is the de-facto standard test framework for Java, with a concise annotation-driven API.

JavamodernJUnitunit test
Duration
⏱ ~1.5-2 hours
Level
πŸ“Š Intermediate-Advanced
Prerequisite
🎯 Previous lecture or equivalent knowledge
OUTCOME
Saying "the code works" requires **verification**. JUnit 5 is the de-facto standard test framework for Java, with a concise annotation-driven API.

What you'll learn

  • 1Know where `src/test/java` lives in a Maven project
  • 2Use `@Test`, `@BeforeEach`, `@AfterEach`, `@DisplayName`
  • 3Distinguish `assertEquals` / `assertTrue` / `assertThrows`
  • 4Run tests with `mvn test`

Overview

Saying "the code works" requires **verification**. JUnit 5 is the de-facto standard test framework for Java, with a concise annotation-driven API.

Core Concepts

1) Maven dependency

xml
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.10.5</version>
    <scope>test</scope>
</dependency>

`scope=test` keeps it out of production artifacts.

2) Basic test

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

class CalculatorTest {
    @Test
    void add() {
        assertEquals(5, 2 + 3);
    }
}

3) Lifecycle annotations

AnnotationWhen it runs
`@BeforeAll`once before all tests (static)
`@BeforeEach`before every test
`@Test`a test method
`@AfterEach`after every test
`@AfterAll`once after all tests (static)

4) Common assertions

java
assertEquals(expected, actual);
assertTrue(condition);
assertNotNull(value);
assertThrows(IllegalArgumentException.class, () -> ...);
assertAll("group", () -> assertEquals(1, a), () -> assertEquals(2, b));

5) Run it

bash
mvn test
# or in IntelliJ: Right-click β†’ Run 'CalculatorTest'

Examples

Example 1 β€” `CalculatorTest.java`

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

class CalculatorTest {
    Calculator c;

    @BeforeEach
    void setUp() { c = new Calculator(); }

    @Test
    @DisplayName("adds two numbers")
    void add() {
        assertEquals(5, c.add(2, 3));
    }

    @Test
    void divByZero() {
        assertThrows(ArithmeticException.class, () -> c.divide(1, 0));
    }
}

class Calculator {
    int add(int a, int b) { return a + b; }
    int divide(int a, int b) { return a / b; }
}

Example 2 β€” Lifecycle

java
import org.junit.jupiter.api.*;

class LifecycleTest {
    @BeforeAll static void initAll() { System.out.println("before all"); }
    @BeforeEach void init() { System.out.println("before each"); }
    @Test void one() { System.out.println("test one"); }
    @Test void two() { System.out.println("test two"); }
    @AfterEach void cleanup() { System.out.println("after each"); }
    @AfterAll static void cleanupAll() { System.out.println("after all"); }
}

**Output**

text
before all
before each
test one
after each
before each
test two
after each
after all

Example 3 β€” `assertAll`

java
@Test
void grouped() {
    assertAll("person",
        () -> assertEquals("Jisoo", person.name()),
        () -> assertEquals(21, person.age())
    );
}

Example 4 β€” `assertThrows` with message check

java
@Test
void rejectsZero() {
    var ex = assertThrows(IllegalArgumentException.class, () -> safeDiv(1, 0));
    assertEquals("b != 0", ex.getMessage());
}

Common Mistakes

  1. Forgetting `<scope>test</scope>` so test code leaks into production
  2. Marking `@BeforeAll` / `@AfterAll` non-static β€” JUnit refuses to run them
  3. Storing state in fields without `@BeforeEach` resets β€” tests become order-dependent
  4. Using `assertEquals(a, b)` when you meant `assertSame` for reference equality
  5. Writing one test method that asserts twenty things β€” split per concern

Summary

  • `@Test` + assertions are the minimum
  • `@BeforeEach` / `@AfterEach` keep tests isolated
  • Run with `mvn test` (or your IDE)

Practice

# Practice - 22. JUnit 5

## Exercise 1 β€” Test `isPrime`

  • File: `IsPrimeTest.java`
  • Key concepts: `@Test`, `assertTrue`/`assertFalse`

Requirements

  • Write `isPrime(int)`.
  • Tests: 2, 3, 5, 7, 11 β†’ true; 1, 4, 6, 8, 9 β†’ false.

## Exercise 2 β€” Test exception path

  • File: `DivideTest.java`
  • Key concepts: `assertThrows`

Requirements

  • Write `divide(a, b)` that throws `IllegalArgumentException` if `b == 0`.
  • Verify the exception and its message.

## Solutions After trying it yourself, compare with [`answer/`](./answer/).

Solution code (homework/answer/)

answer/IsPrimeTest.java

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

class IsPrimeTest {
    static boolean isPrime(int n) {
        if (n < 2) return false;
        for (int i = 2; i * i <= n; i++) if (n % i == 0) return false;
        return true;
    }

    @Test void primes() {
        for (int n : new int[]{2, 3, 5, 7, 11}) assertTrue(isPrime(n));
    }
    @Test void composites() {
        for (int n : new int[]{1, 4, 6, 8, 9}) assertFalse(isPrime(n));
    }
}

answer/DivideTest.java

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

class DivideTest {
    static int divide(int a, int b) {
        if (b == 0) throw new IllegalArgumentException("b != 0");
        return a / b;
    }

    @Test void normal() { assertEquals(5, divide(10, 2)); }

    @Test void zeroDenominator() {
        var ex = assertThrows(IllegalArgumentException.class, () -> divide(1, 0));
        assertEquals("b != 0", ex.getMessage());
    }
}

Try It Yourself

bash
mvn test

Next Lecture

[23_Spring_Boot](../../06_Spring_Boot/23_Spring_Boot_μ‹œμž‘ν•˜κΈ°/) β€” chapter 6 begins: getting started with Spring Boot.

Example code / lecture materials

All lecture materials and example code are openly available on GitHub.

View on GitHub β†—