For decades, JUnit has been the gold standard of testing in Java. With JUnit 5, developers gained a modern, extensible, and powerful framework that makes unit testing more expressive and maintainable. Whether you’re new to testing or migrating from JUnit 4, JUnit 5 is the foundation you’ll build on for reliable software in today’s CI/CD-driven world.
In this tutorial, we’ll cover how to set up JUnit 5 in your project, write your first test, explore core annotations, and integrate it into modern development workflows.
Why JUnit 5 Matters
- Bug Prevention: Catch issues early before production.
- Maintainability: Safer refactoring with automated safety nets.
- CI/CD Integration: Seamless execution in Jenkins, GitHub Actions, and GitLab CI.
- Modern Features: Parameterized tests, nested tests, extensions, and dependency injection.
Setting Up JUnit 5
JUnit 5 consists of three main modules:
- JUnit Platform – Test engine and launcher.
- JUnit Jupiter – New programming model and annotations.
- JUnit Vintage – Backward compatibility with JUnit 4.
Maven Setup
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M7</version>
</plugin>
</plugins>
</build>
Gradle Setup
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
}
test {
useJUnitPlatform()
}
Writing Your First JUnit 5 Test
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class Calculator {
int add(int a, int b) {
return a + b;
}
}
class CalculatorTest {
@Test
void testAddition() {
Calculator calculator = new Calculator();
int result = calculator.add(2, 3);
assertEquals(5, result, "2 + 3 should equal 5");
}
}
Explanation
@Test
– Marks a method as a test.assertEquals
– Checks expected vs. actual results.- Test passes if values match, fails otherwise.
Core Annotations in JUnit 5
@Test
– Marks a method as a test case.@BeforeEach
/@AfterEach
– Runs before/after each test method.@BeforeAll
/@AfterAll
– Runs once before/after all tests (must be static).@Disabled
– Skips a test.@ParameterizedTest
– Runs a test with multiple inputs.@Nested
– Allows grouping of related tests.
Example: Lifecycle Annotations
import org.junit.jupiter.api.*;
class LifecycleTest {
@BeforeAll
static void initAll() { System.out.println("Before all tests"); }
@BeforeEach
void init() { System.out.println("Before each test"); }
@Test
void testExample() { System.out.println("Running test"); }
@AfterEach
void tearDown() { System.out.println("After each test"); }
@AfterAll
static void tearDownAll() { System.out.println("After all tests"); }
}
Parameterized Tests
JUnit 5 supports parameterized tests, reducing code duplication.
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.assertTrue;
class ParameterizedExampleTest {
@ParameterizedTest
@ValueSource(strings = {"racecar", "radar", "level"})
void testPalindrome(String word) {
assertTrue(isPalindrome(word));
}
boolean isPalindrome(String word) {
return word.equals(new StringBuilder(word).reverse().toString());
}
}
Advanced Features
- Dynamic Tests: Generate tests programmatically at runtime.
- Dependency Injection: Inject test info and temp directories into tests.
- Extensions API: Customize test execution (e.g., SpringExtension for Spring Boot).
- Coverage with JaCoCo: Measure and enforce test coverage.
Real-World Scenarios
- Spring Boot: Use
@SpringBootTest
with JUnit 5 to load application context. - Mockito: Combine with JUnit 5 for mocking dependencies.
- Testcontainers: Run integration tests against real Dockerized services.
- CI/CD Pipelines: Run JUnit tests automatically on commits with GitHub Actions.
Best Practices
- Keep tests small and isolated.
- Use descriptive test names (
shouldAddTwoNumbers
). - Avoid unnecessary dependencies in unit tests.
- Run tests frequently with Maven/Gradle.
- Include tests in CI/CD pipelines.
Version Tracker
- JUnit 4 → JUnit 5 Transition: New annotations (
@BeforeAll
,@AfterAll
), Jupiter engine, and modular design. - Mockito Enhancements: Support for static/final mocking.
- Testcontainers Growth: Expanded modules for databases, message brokers, and cloud services.
Conclusion & Key Takeaways
JUnit 5 is the modern foundation for Java testing. By setting it up with Maven/Gradle and writing your first test, you’ve taken the first step toward building maintainable, production-ready test suites.
Key Takeaways:
- JUnit 5 offers powerful annotations and modern features.
- It integrates seamlessly with build tools and CI/CD pipelines.
- Combined with Mockito and Testcontainers, it supports unit, integration, and E2E testing.
FAQ
1. How is JUnit 5 different from JUnit 4?
JUnit 5 introduces the Jupiter API, modular architecture, and better annotations.
2. Do I need Maven or Gradle for JUnit 5?
No, but build tools make setup and automation easier.
3. Can JUnit 5 run JUnit 4 tests?
Yes, using the JUnit Vintage engine.
4. How do I measure coverage with JUnit 5?
Use JaCoCo with Maven/Gradle to generate coverage reports.
5. How do I mock dependencies in JUnit 5?
Use Mockito with @ExtendWith(MockitoExtension.class)
.
6. Can I run JUnit 5 tests in IntelliJ/Eclipse?
Yes, modern IDEs provide built-in support.
7. What’s a parameterized test?
A test that runs multiple times with different input values.
8. Does JUnit 5 support dependency injection?
Yes, via TestInfo
, TestReporter
, and extensions like Spring.
9. Can I use JUnit 5 in legacy projects?
Yes, alongside JUnit 4 tests with the Vintage engine.
10. Should I migrate from JUnit 4 to 5?
Yes, for long-term maintainability and modern features.