5 Java JUnit 5 Features I Wish I Knew Earlier
Since the release of JUnit 5 nearly ten years ago, numerous new features have been added, many of which are not well known to the broader community (myself included). Today, I will show you five features that I personally find extremely useful.
Temporary Directory Support
JUnit Version: 5.4
The @TempDir
annotation provides a clean and efficient way to create and manage temporary directories for file-based tests. It automatically handles cleanup, reducing boilerplate code and potential resource leaks.
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertTrue;
class TempDirTest {
@Test
void testUsingTempDirectory(@TempDir Path tempDir) throws IOException {
Path testFile = tempDir.resolve("test.txt");
Files.write(testFile, "Hello, World!".getBytes());
assertTrue(Files.exists(testFile));
}
}
Benefits:
- Automatic cleanup of test files
- Reduced boilerplate code
- Improved test isolation
If you are planning a migration from Java 8 to a newer version, take a look at this series of articles I have covering that topic
Parallel Test Execution
JUnit Version: 5.3
Parallel test execution can significantly speed up your test suite, especially for larger projects. By running tests concurrently, you can make better use of multi-core processors and reduce overall test execution time. It's good practice to write unit tests such that they can be executed in parallel: no shared states, no shared folders, etc. Even if you don't need to run them in parallel now, migrating to parallel execution in the future will be much smoother.
// In junit-platform.properties file
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent
// In test class
@Execution(ExecutionMode.CONCURRENT)
class ParallelExecutionTest {
@Test
void test1() { /* ... */ }
@Test
void test2() { /* ... */ }
}
Benefits:
- Faster test execution
- Better use of system resources
- More realistic representation of real-world execution
Tag Expressions
JUnit Version: 5.0
Tag expressions allow for more complex filtering of tests, giving you fine-grained control over which tests to run. This is particularly useful in CI/CD pipelines where you might want to run different subsets of tests based on the build stage.
@Tag("integration")
@Tag("slow")
class TaggedTests {
@Test
void taggedTest() { /* ... */ }
}
// Run with: mvn test -Dgroups="integration & !slow"
Benefits:
- Flexible test selection
- Improved CI/CD pipeline control
- Better organization of test suites
Conditional Test Execution
JUnit Version: 5.1
Conditional test execution allows you to run or skip tests based on various conditions, such as system properties or environment variables. This feature is invaluable when dealing with platform-specific tests or environment-dependent scenarios. In my case, we use it to verify if a Kafka Broker exists by providing the KAFKA_HOST
, KAFKA_PORT
variable, and only then we run the tests.
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
public class SystemPropertyTests {
@Test
@EnabledIfSystemProperty(named = "os.name", matches = "Linux")
void onlyOnLinux() {
System.out.println("Running on Linux");
}
@Test
@DisabledIfSystemProperty(named = "java.version", matches = "1.8.*")
void notOnJava8() {
System.out.println("Not running on Java 8");
}
}
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable;
public class EnvironmentVariableTests {
@Test
@EnabledIfEnvironmentVariable(named = "ENV", matches = "DEV")
void onlyOnDevEnvironment() {
System.out.println("Running in DEV environment");
}
@Test
@DisabledIfEnvironmentVariable(named = "ENV", matches = "PROD")
void notOnProdEnvironment() {
System.out.println("Not running in PROD environment");
}
}
Benefits:
- Platform-specific testing
- Environment-aware test execution
- Reduced false negatives in CI/CD pipelines
Test Class Ordering
JUnit Version: 5.8
Test class ordering allows you to specify the execution order of nested test classes, which can be crucial when you have interdependent test scenarios or want to ensure a specific flow in your test suite.
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestClassOrder;
import org.junit.jupiter.api.ClassOrderer;
@TestClassOrder(ClassOrderer.OrderAnnotation.class)
public class OrderedTestClasses {
@Nested
@Order(1)
class FirstTest {
@Test
void test() { /* ... */ }
}
@Nested
@Order(2)
class SecondTest {
@Test
void test() { /* ... */ }
}
}
Benefits:
- Control over test execution flow
- Support for scenario-based testing
- Improved readability for complex test suites
By using these often-overlooked features, we can write more effective, efficient, and maintainable tests. Each of these features addresses common pain points in testing, from performance issues to test organization and conditional execution. I encourage you to explore these features in your next JUnit 5 project and experience the benefits firsthand.
What's next
Checkout my latest post about Java