Skip to content
Chapter 04 · JUnit 5 & Mockito

Test Lifecycle, @TestInstance and Parallel Execution ​

With the architecture in place, this chapter writes tests. It covers, in order: the dependencies a project needs, the lifecycle methods that run around tests (@BeforeAll, @BeforeEach, @AfterEach, @AfterAll), how many test-class instances JUnit creates (@TestInstance), running tests in parallel, and the AAA pattern that every test follows.

Dependencies ​

In a Spring Boot project, spring-boot-starter-test brings in everything needed to run JUnit 5 tests: org.junit.jupiter:junit-jupiter (the Jupiter engine and API) and org.junit.platform:junit-platform-launcher.

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

In a plain Maven project, add org.junit.jupiter:junit-jupiter with test scope.

The lifecycle annotations ​

AnnotationRunsTypical use
@BeforeAllonce, before all tests of the classexpensive setup shared by all tests, such as a database connection
@BeforeEachbefore each testfresh test data: new objects, temporary rows in a DB
@Testthe test itself
@AfterEachafter each testcleanup: close files, delete temporary data
@AfterAllonce, after all testsrelease shared resources, such as closing the DB connection

Two lifecycles ​

Lifecycle.PER_METHOD (the default) ​

A new instance of the test class is created for each test method.

@BeforeAll runs once, before any instance exists, so it must be static; so must @AfterAll. A non-static @BeforeAll makes test discovery fail: "@BeforeAll method ... must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS)".

java
public class Junit5LifecycleDemo {

    public Junit5LifecycleDemo() {
        System.out.println("Junit5LifecycleDemo class Instance created");
    }

    @BeforeAll
    static void beforeAll() {
        System.out.println("inside @BeforeAll method");
    }

    @BeforeEach
    void beforeEach() {
        System.out.println("inside @BeforeEach method");
    }

    @Test
    void testcase1() {
        System.out.println("inside Testcase 1");
    }

    @Test
    void testcase2() {
        System.out.println("inside Testcase 2");
    }

    @AfterEach
    void afterEach() {
        System.out.println("inside @AfterEach method");
    }

    @AfterAll
    static void afterAll() {
        System.out.println("inside @AfterAll method");
    }
}
text
inside @BeforeAll method
Junit5LifecycleDemo class Instance created
inside @BeforeEach method
inside Testcase 1
inside @AfterEach method
Junit5LifecycleDemo class Instance created
inside @BeforeEach method
inside Testcase 2
inside @AfterEach method
inside @AfterAll method

Lifecycle.PER_CLASS ​

One instance of the test class for all its tests:

java
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class Junit5LifecycleDemo {
    // same methods, but @BeforeAll and @AfterAll may now be non-static
}
text
Junit5LifecycleDemo class Instance created
inside @BeforeAll method
inside @BeforeEach method
inside Testcase 1
inside @AfterEach method
inside @BeforeEach method
inside Testcase 2
inside @AfterEach method
inside @AfterAll method

The instance is created first, and only once; since it exists before @BeforeAll, those methods can be non-static.

Which to use ​

PER_METHOD is preferred:

  • Test isolation: every test starts from a fresh object, so tests can run in any order. That is what "testing in isolation" means.
  • Easy debugging: a failure cannot be caused by state left behind by another test.
  • Safe for parallel execution.

Use PER_CLASS only when tests must share state, and then make sure the test code is thread-safe, since parallel tests would share the one instance.

Several methods with the same annotation ​

A class may have several @BeforeAll, @BeforeEach, @AfterEach or @AfterAll methods. They all run, but the order between them is not guaranteed (JUnit uses a deterministic but deliberately non-obvious order). Do not rely on it.

Parallel execution ​

By default tests run sequentially, one after another, on the main thread. With PER_METHOD's isolation, they can safely run in parallel.

Who runs them in parallel? Not the launcher: the launcher only calls each engine's execute. The engine decides how to run its tests; Jupiter can run them concurrently on a thread pool, which is why the settings are junit.jupiter.* and apply to Jupiter tests only.

java
public class Junit5LifecycleDemo {

    @Test
    void testcaseA() throws InterruptedException {
        System.out.println("inside testcaseA, executed by: " + Thread.currentThread().getName());
        Thread.sleep(1000);
        System.out.println("Finished testcaseA");
    }

    @Test
    void testcaseB() throws InterruptedException {
        System.out.println("inside testcaseB, executed by: " + Thread.currentThread().getName());
        Thread.sleep(1000);
        System.out.println("Finished testcaseB");
    }
}

Create src/test/resources/junit-platform.properties (exactly this name; JUnit reads it):

properties
# enable parallel execution (default: false)
junit.jupiter.execution.parallel.enabled = true

# methods within a test class run in parallel (default: same_thread, i.e. sequentially)
junit.jupiter.execution.parallel.mode.default = concurrent

# test classes run in parallel with each other (default: same_thread)
junit.jupiter.execution.parallel.mode.classes.default = concurrent

# thread pool strategy:
#   dynamic (default): pool size computed from the available processors
#   fixed: you choose the number of threads
junit.jupiter.execution.parallel.config.strategy = fixed
junit.jupiter.execution.parallel.config.fixed.parallelism = 5

Sequential:

text
inside testcaseA, executed by: main
Finished testcaseA
inside testcaseB, executed by: main
Finished testcaseB

Parallel:

text
inside testcaseA, executed by: ForkJoinPool-1-worker-2
inside testcaseB, executed by: ForkJoinPool-1-worker-3
Finished testcaseB
Finished testcaseA

Both start at once on different worker threads, each with its own instance and its own @BeforeEach/@AfterEach.

The AAA pattern ​

Every test follows Arrange, Act, Assert:

java
@Test
void testIsAdult() {
    // Arrange: set up objects and data (and mocks)
    User user = new User("XYZ", 20);

    // Act: call the method under test
    boolean result = user.isAdult();

    // Assert: check the result against the expectation
    assertTrue(result);
}
  • Arrange: create the objects, test data and mocks.
  • Act: invoke the method being tested.
  • Assert: check that the result matches the expectation. If a bug made isAdult() return false for age 20, this test fails.

Next: The last step of AAA is assert. Chapter 5 covers JUnit 5's assertions in depth, including how to test that code throws the right exception.

Check yourself ​

  1. In what order do @BeforeAll, the constructor, @BeforeEach, the test and @AfterEach run with the default lifecycle?
  2. Why must @BeforeAll be static by default, and when can it be non-static?
  3. How many instances are created for a class with 10 tests under PER_METHOD? Under PER_CLASS?
  4. Why is PER_METHOD preferred?
  5. When would you choose PER_CLASS, and what must you then take care of?
  6. Two methods are annotated @BeforeEach. Which runs first?
  7. Which component actually runs Jupiter tests in parallel, and where do you enable it?
  8. What is the difference between parallel.mode.default and parallel.mode.classes.default?
  9. What do the three A's of AAA stand for?
Answers
  1. @BeforeAll, then for each test: constructor, @BeforeEach, the test, @AfterEach; finally @AfterAll.
  2. It runs before any instance exists. With @TestInstance(Lifecycle.PER_CLASS).
  3. Ten. One.
  4. Each test gets a fresh instance: isolation, any order, easier debugging, safe in parallel.
  5. When tests must share state; the test code must then be thread-safe.
  6. Not guaranteed; do not depend on it.
  7. The Jupiter engine; in src/test/resources/junit-platform.properties with junit.jupiter.execution.parallel.enabled=true and the mode settings.
  8. The first runs the methods within a class in parallel; the second runs different classes in parallel.
  9. Arrange, Act, Assert.