A brief introduction of JUnit 5

A brief introduction of JUnit 5

JUnit is a testing framework.

JUnit 5 Architecture

junit5_architecture

  • Jupiter: JUnit APIs that runs new junit tests.
  • Vintage: Another set of APIs, which runs older junit tests.

How to write JUnit test

  1. Create an instance of the class under test
  2. Set up inputs
  3. Execute the code you want to test
  4. Verify the result is what you expect

Test life cycle

test_lifecycle

JUnit 5 provides many lifecycle hooks, and you can use them by adding annotations:

1
2
3
4
@BeforeAll
@BeforeEach
@AfterAll
@AfterEach

Note: Methods with annotation @BeforeAll and @AfterAll must be static so that they do not need to rely on any instance.

JUnit test demo

1. Build Maven project

Basically, if you want to build a project using Maven, you should add two dependencies in pom.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<dependencies>
<!-- junit-jupiter-engine:
Implementation of the TestEngine API for JUnit Jupiter.-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.5.2</version>
<scope>test</scope>
</dependency>

<!-- junit-jupiter-api:
API for writing tests using JUnit Jupiter.-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.5.2</version>
<scope>test</scope>
</dependency>
</dependencies>

You should also add maven-compiler-plugin to specify your JDK version(in this case we use JDK 11):

1
2
3
4
5
6
7
8
9
10
11
12
13
<build>
<plugins>
<plugin>
<groupId>org.apache.mavenplugins</groupId>
<artifactId>maven-compiler-pugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>

2. MathUtils.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MathUtils {

public int add(int a, int b) {
return a + b;
}

public int subtract(int a, int b) {
return a - b;
}

public int multiply(int a, int b) {
return a * b;
}

public int divide(int a, int b) {
return a / b;
}

public double computeCircleArea(double radius) {
return Math.PI * radius * radius;
}
}

3. MathUtilsTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import org.junit.jupiter.api.*;

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

// To enable JUnit Jupiter execute all test methods on the same test instance(in this case, instance of MathUtilsTest).
// The default mode of @TestInstance is PER_METHOD, which means that JUnit would create an instance of MathUtilsTest for each method.
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class MathUtilsTest {

// We should declare mathUtils outside all methods so that each method can share it.
MathUtils mathUtils;

@BeforeAll
// Methods with annotation @BeforeAll and @AfterAll must be static so that they do not need to rely on any instance.
static void beforeAllInit() {
System.out.println("This needs to run before all.");
}

@BeforeEach
void init() {
mathUtils = new MathUtils();
}

@AfterEach
void cleanup() {
System.out.println("Cleaning up...");
}

// @Test: Informs the JUnit engine what methods need to run.
@Test
void testAdd() {
int expected = 2;
int actual = mathUtils.add(1, 1);
assertEquals(expected, actual);
}

@Test
void testSubtract() {
assumeTrue(mathUtils.subtract(2, 1) == 1);
}

@Test
@Disabled // Skip this test.
void testDivide() {
assertThrows(ArithmeticException.class, () -> mathUtils.divide(1, 0), "Divide by zero should throw.");
}

@Test
void testMultiply() {
// use lambda in assertAll
assertAll(
() -> assertEquals(4, mathUtils.multiply(2, 2), "Should return the right product."),
() -> assertEquals(0, mathUtils.multiply(0, 2)),
() -> assertEquals(-2, mathUtils.multiply(2, -1))
);
}

@Test
@DisplayName("Testing computeCircleArea.")
void testComputeCircleArea() {
assertEquals(314.1592653589793, mathUtils.computeCircleArea(10), "Should return right circle area.");
}

@Nested
class AddTest {
@Test
void testAddPositive() {
int expected = 2;
int actual = mathUtils.add(1, 1);
// lambda: messageSupplier
assertEquals(expected, actual, () -> "Should return " + expected + ", but return " + actual);
}

@Test
void testAddNegative() {
assertEquals(-2, mathUtils.add(-1, -1));
}

}

}

Limitation of Unit Test

Unit tests can only show the presence of errors; it cannot show the absence of errors. In another word, tests can only test the cases that you can think of, but they cannot test the cases that you cannot imagine.