Listing 5. Logging the invocations of JUnit 5 lifecycle methods (LifecycleDemoTest.java)
package com.javaworld.geekcap.lifecycle;
import org.junit.jupiter.api.*;
public class LifecycleDemoTest {
@BeforeAll
static void beforeAll() {
System.out.println("Connect to the database");
}
@BeforeEach
void beforeEach() {
System.out.println("Load the schema");
}
@AfterEach
void afterEach() {
System.out.println("Drop the schema");
}
@AfterAll
static void afterAll() {
System.out.println("Disconnect from the database");
}
@Test
void testOne() {
System.out.println("Test One");
}
@Test
void testTwo() {
System.out.println("Test Two");
}
}
The output from running this test prints the following:
Connect to the database
Load the schema
Test One
Drop the schema
Load the schema
Test Two
Drop the schema
Disconnect from the database
As you can see from this output, the beforeAll
method is called first and may do something like connect to a database or create a large data structure into memory. Next, the beforeEach
method prepares the data for each test; for example, by populating a test database with an expected set of data. The first test then runs, followed by the afterEach
method. This process (beforeEach
—> test—>afterEach
) continues until all the tests have completed. Finally, the afterAll
method cleans up the test environment, possibly by disconnecting from a database.
Before wrapping up this initial introduction to testing with JUnit 5, I’ll show you how to use tags to selectively run different kinds of test cases. Tags are used to identify and filter specific tests that you want to run in various scenarios. For example, you might tag one test class or method as an integration test and another as development code. The names and uses of the tags are all up to you.
We’ll create three new test classes and tag two of them as development and one as integration, presumably to differentiate between tests you want to run when building for different environments. Listings 6, 7, and 8 show these three simple tests.
Listing 6. JUnit 5 tags, test 1 (TestOne.java)
package com.javaworld.geekcap.tags;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
@Tag("Development")
class TestOne {
@Test
void testOne() {
System.out.println("Test 1");
}
}
Listing 7. JUnit 5 tags, test 2 (TestTwo.java)
package com.javaworld.geekcap.tags;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
@Tag("Development")
class TestTwo {
@Test
void testTwo() {
System.out.println("Test 2");
}
}
Listing 8. JUnit 5 tags, test 3 (TestThree.java)
package com.javaworld.geekcap.tags;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
@Tag("Integration")
class TestThree {
@Test
void testThree() {
System.out.println("Test 3");
}
}
Tags are implemented through annotations, and you can annotate either an entire test class or individual methods in a test class; furthermore, a class or a method can have multiple tags. In this example, TestOne and TestTwo are annotated with the “Development”
tag, and TestThree is annotated with the “Integration”
tag. We can filter test runs in different ways based on tags. The simplest of these is to specify a test in your Maven command line; for example, the following only executes tests tagged as “Development”
:
mvn clean test -Dgroups="Development"
The groups
property allows you to specify a comma-separated list of tag names for the tests that you want JUnit 5 to run. Executing this yields the following output:
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.javaworld.geekcap.tags.TestOne
Test 1
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.029 s - in com.javaworld.geekcap.tags.TestOne
[INFO] Running com.javaworld.geekcap.tags.TestTwo
Test 2
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s - in com.javaworld.geekcap.tags.TestTwo
Likewise, we could execute just the integration tests as follows:
mvn clean test -Dgroups="Integration"
Or, we could execute both development and integration tests:
mvn clean test -Dgroups="Development, Integration"
In addition to the groups
property, JUnit 5 allows you to use an excludedGroups
property to execute all tests that do not have the specified tag. For example, in a development environment, we do not want to execute the integration tests, so we could execute the following:
mvn clean test -DexcludedGroups="Integration"
This is helpful because a large application can have literally thousands of tests. If you wanted to create this environmental differentiation and add some new production tests, you would not want to have to go back and add a “Development”
tag to the other 10,000 tests.
Finally, you can add these same groups
and excludedGroups
fields to the surefire
plugin in your Maven POM file. You can also control these fields using Maven profiles. I encourage you to review the JUnit 5 user guide to learn more about tags.
Conclusion
This article introduced some of the highlights of working with JUnit 5. I showed you how to configure a Maven project to use JUnit 5 and how to write tests using the @Test
and @ParameterizedTest
annotations. I then introduced the JUnit 5 lifecycle annotations, followed by a look at the use and benefits of filter tags.