Introduction
Junit is a popular framework to create tests for Java applications. Although individual unit tests are mostly straightforward, integration and functional tests are a little bit more involved because they usually require multiple components working together. For that reason, understanding the life cycle of Junit tests can be greatly beneficial.
In this tutorial, we will learn about the lifecycle of Junit 5 test instances.
Goals
At the end of the tutorial, you would have learned:
- Different stages of a Junit instance lifecycle.
Prerequisite Knowledge
- Basic Java.
- Basic Junit.
Tools Required
- A Java IDE such as IntelliJ Community Edition.
Project Setup
To follow along with the tutorial, perform the steps below:
- Create a new Gradle Java project. I am using
Java 17
andGradle 7.2
, but any Java version 8+ should work. - Add the dependency for
unit-jupiter-engine
. The latest version is5.8.1
as of this writing.
Below is the content of my build.gradle
file.
plugins {
id 'java'
}
group 'org.example'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}
test {
useJUnitPlatform()
}
- Under
src/test/java
, create a new package calledcom.example
. - Under
com.example
, create a new class calledLifecycleTest
. - We do not need the
main()
method for this tutorial.
Lifecycle Overview
There are 5 stages of a Junit 5 test instance lifecycle. The list below orders them from first to last. The @ symbol means that there is an annotation matching that lifecycle stage as well.
@BeforeAll
: executed before all tests.@BeforeEach
: executed before each test.@Test
: the test itself.@AfterEach
: executed after each test.@AfterAll
: executed after all tests.
To see how they work together, we need to add some tests into our code. In LifecycleTest.java
, add the code below.
package com.example;
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
//@TestInstance(PER_CLASS)
class LifecycleTest {
@BeforeAll
void beforeAll(){
System.out.println("Before All");
}
@BeforeEach
void beforeEach(){
System.out.println("Before Each");
}
@Test
void test(){
System.out.println("Test");
}
@AfterEach
void afterEach(){
System.out.println("After Each");
}
@AfterAll
void afterAll(){
System.out.println("After All");
}
}
But there is a problem with the code above. If we run the test, it will actually throw a JunitException
.
@BeforeAll method 'void com.example.LifecycleTest.beforeAll()' must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS).
The reason for the JunitException
is that the methods annotated with @BeforeAll
and @AfterAll
must either be static or the enclosing class must be annotated with @TestInstance(Lifecycle.PER_CLASS)
. By default, Junit creates a new instance of LifecycleTest
for every test with @TestInstance(Lifecycle.PER_METHOD)
. So if we want the code to run correctly, we would need to add @TestInstance(Lifecycle.PER_CLASS)
on top of the LifecycleTest
class declaration.
Go ahead and uncomment the line
//@TestInstance(Lifecycle.PER_CLASS)
in the code. When we run the test again, we should now see the test passing and the output prints out the lines in expected order.
Before All
Before Each
Test
After Each
After All
Repeated Tests
While the lifecycle is simple to understand when executing only a single test, repeated tests behave a little bit differently. When running repeated tests, @BeforeAll
and @AfterAll
are only executed once, while @BeforeEach
and @AfterEach
are always executed for each test. To demonstrate, let us comment out the @Test
method in our code.
// @Test
// void test(){
// System.out.println("Test");
// }
And add a repeated test method. This test method will run twice because of the int
value we passed to the @RepeatedTest
annotation.
@RepeatedTest(2)
void repeatTest(){
System.out.println("Repeat Test");
}
When we run the test, we will see @BeforeAll
and @AfterAll
were only executed once, while @BeforeEach
and@AfterEach
are both executed twice(I have added some line separators to make the output easier to read).
Before All
Before Each
Repeat Test
After Each
Before Each
Repeat Test
After Each
After All
Solution Code
package com.example;
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
@TestInstance(PER_CLASS)
class LifecycleTest {
@BeforeAll
void beforeAll(){
System.out.println("Before All");
}
@BeforeEach
void beforeEach(){
System.out.println("Before Each");
}
// @Test
// void test(){
// System.out.println("Test");
// }
@RepeatedTest(2)
void repeatTest(){
System.out.println("Repeat Test");
}
@AfterEach
void afterEach(){
System.out.println("After Each");
}
@AfterAll
void afterAll(){
System.out.println("After All");
}
}
Summary
We have learned about the different stages of a Junit 5 test instance lifecycle. The full project code can be found here https://github.com/dmitrilc/DaniwebJunitLifecycle/tree/master