Unit Testing in Java with JUnit 5: Annotations, Assertions, and Mockito
In enterprise Java software engineering, writing clean, maintainable, and bug-free code is only half the battle. Ensuring that business logic behaves predictably under edge cases requires a robust automated testing strategy. **JUnit 5** is the modern standard testing framework for Java, introducing a modular architecture (`JUnit Platform`, `JUnit Jupiter`, and `JUnit Vintage`), powerful annotations, parameterized testing, and nested test structures.
This comprehensive guide explores the core components of JUnit 5 combined with **Mockito** for dependency isolation, equipping Java developers to build thorough, production-grade test suites.
1. JUnit 5 Core Annotations and Lifecycle Hooks
JUnit 5 relies on clean, intuitive annotations to manage test execution lifecycles and organize test methods.
import org.junit.jupiter.api.*;
public class CalculatorTest {
@BeforeAll
static void initAll() {
System.out.println("Initializing test suite resources...");
}
@BeforeEach
void setUp() {
// Executed before each individual test method
}
@Test
@DisplayName("Test addition of two positive integers")
void testAddition() {
int result = 2 + 3;
Assertions.assertEquals(5, result, "Calculated sum should be 5");
}
@AfterEach
void tearDown() {
// Cleanup after each test
}
@AfterAll
static void tearDownAll() {
System.out.println("Test suite execution completed.");
}
}
2. Advanced Assertions and Exception Testing
JUnit 5 provides static assertion methods (`Assertions.*`) with lambda support for lazy evaluation, alongside robust tools for testing expected exceptions.
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class ValidationTest {
@Test
void testExceptionThrowing() {
IllegalArgumentException exception = Assertions.assertThrows(
IllegalArgumentException.class,
() -> {
throw new IllegalArgumentException("Invalid age provided");
}
);
Assertions.assertEquals("Invalid age provided", exception.getMessage());
}
}
3. Parameterized Tests in JUnit 5
Instead of duplicating test code for different input variations, `@ParameterizedTest` allows you to execute a single test method across multiple arguments using sources like `@ValueSource` or `@CsvSource`.
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
public class StringUtilsTest {
@ParameterizedTest
@CsvSource({
"radar, true",
"hello, false",
"level, true",
"java, false"
})
void testIsPalindrome(String input, boolean expected) {
boolean result = input.equals(new StringBuilder(input).reverse().toString());
Assertions.assertEquals(expected, result);
}
}
4. Mocking External Dependencies with Mockito
Enterprise services often depend on external databases, repositories, or HTTP clients. Mocking frameworks like **Mockito** isolate unit tests by creating mock implementations of dependencies.
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
// Mocking interface service
interface UserRepository {
String findUserEmailById(int userId);
}
class UserService {
private final UserRepository repository;
public UserService(UserRepository repository) {
this.repository = repository;
}
public String getUserDomain(int userId) {
String email = repository.findUserEmailById(userId);
return email.substring(email.indexOf("@") + 1);
}
}
public class UserServiceTest {
@Test
void testGetUserDomain() {
// 1. Create mock repository
UserRepository mockRepo = Mockito.mock(UserRepository.class);
// 2. Stub behavior
Mockito.when(mockRepo.findUserEmailById(101)).thenReturn("admin@codecrown.in");
// 3. Inject mock into service
UserService service = new UserService(mockRepo);
// 4. Assert outcome
String domain = service.getUserDomain(101);
Assertions.assertEquals("codecrown.in", domain);
// 5. Verify interaction
Mockito.verify(mockRepo, Mockito.times(1)).findUserEmailById(101);
}
}
Conclusion
Mastering unit testing in Java with JUnit 5 and Mockito—from organizing test lifecycles and writing parameterized checks to isolating business logic with mocks—enables software engineers to build secure, highly reliable enterprise applications.