In this article, we will discuss the usage of JUnit @Nested annotation with an example.
JUnit 5 Nested Tests Example
To demo for the JUnit 5 nested tests feature, assume that we have an UserService class that has 4 methods: - login()
- logout()
- changePassword()
- resetPassword()
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.platform.commons.util.StringUtils;
public class UserService {
public boolean login(String username, String password) {
if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
throw new IllegalArgumentException("Username and password must not be null or empty");
} else if (username.equals("admin") && password.equals("password123")) {
return true;
}
return false;
}
public boolean changePassword(long userId, String oldPassword, String newPassword) {
if (userId == 1 && StringUtils.isNotBlank(newPassword) && StringUtils.isNotBlank(newPassword)
&& !newPassword.equals(oldPassword)) {
return true;
}
return false;
}
public boolean resetPassword(long userId) {
List<Long> existingUsers = new ArrayList<>(Arrays.asList(1L, 2L, 3L));
if (existingUsers.contains(userId)) {
return true;
}
return false;
}
public boolean logout(long userId) {
List<Long> existingUsers = new ArrayList<>(Arrays.asList(1L, 2L, 3L));
if (existingUsers.contains(userId)) {
// do whatever
}
return true;
}
}
We also assume that 2 methods: login() and changePassword() are complex and we want to group all the test methods of each into different groups by using the JUnit 5 @Nested annotation.
Let’s see the test class for the above class.import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;
@RunWith(JUnitPlatform.class)
public class TestUserService {
private UserService userService = null;
@BeforeEach
public void init() {
userService = new UserService();
}
@Test
public void logoutSuccess() {
long userId = 1L;
assertTrue(userService.logout(userId));
}
@Test
public void resetPasswordExistingUser() {
long userId = 1l;
assertTrue(userService.resetPassword(userId));
}
@Test
public void resetPasswordUserNotExist() {
long userId = 5l;
assertFalse(userService.resetPassword(userId));
}
@Nested
@DisplayName("Test Login Feature")
class LoginFeature {
@Test
void loginUsernamePasswordAreCorrect() {
boolean actual = userService.login("admin", "password123");
assertTrue(actual);
}
@Test
void loginUsernamePasswordAreInCorrect() {
boolean actual = userService.login("admin", "password123456");
assertFalse(actual);
}
@Test
void loginUsernamePasswordAreNulls() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
userService.login(null, null);
});
assertEquals("Username and password must not be null or empty", exception.getMessage());
}
@Test
void loginUsernamePasswordAreEmpty() {
assertThrows(IllegalArgumentException.class, () -> {
userService.login("", "");
});
}
}
@Nested
@DisplayName("Test ChangePassword Feature")
class ChangePasswordFeature {
@Test
void changePasswordUserExistOldPasswordNewPasswordCorrect() {
long userId = 1L; // existed user
assertTrue(userService.changePassword(userId, "password123", "password123456"));
}
@Test
void changePasswordUserNotExist() {
long userId = 999L; // not existed user
assertFalse(userService.changePassword(userId, "password123", "password123456"));
}
@Test
void changePasswordUserExistOldPasswordAndNewPasswordEmpty() {
long userId = 1L; // existed user
assertFalse(userService.changePassword(userId, "", ""));
}
@Test
void changePasswordUserExistOldPasswordEqualNewPassword() {
long userId = 1L; // existed user
assertFalse(userService.changePassword(userId, "password123", "password123"));
}
}
}
- login()
- logout()
- changePassword()
- resetPassword()
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.platform.commons.util.StringUtils;
public class UserService {
public boolean login(String username, String password) {
if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
throw new IllegalArgumentException("Username and password must not be null or empty");
} else if (username.equals("admin") && password.equals("password123")) {
return true;
}
return false;
}
public boolean changePassword(long userId, String oldPassword, String newPassword) {
if (userId == 1 && StringUtils.isNotBlank(newPassword) && StringUtils.isNotBlank(newPassword)
&& !newPassword.equals(oldPassword)) {
return true;
}
return false;
}
public boolean resetPassword(long userId) {
List<Long> existingUsers = new ArrayList<>(Arrays.asList(1L, 2L, 3L));
if (existingUsers.contains(userId)) {
return true;
}
return false;
}
public boolean logout(long userId) {
List<Long> existingUsers = new ArrayList<>(Arrays.asList(1L, 2L, 3L));
if (existingUsers.contains(userId)) {
// do whatever
}
return true;
}
}
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;
@RunWith(JUnitPlatform.class)
public class TestUserService {
private UserService userService = null;
@BeforeEach
public void init() {
userService = new UserService();
}
@Test
public void logoutSuccess() {
long userId = 1L;
assertTrue(userService.logout(userId));
}
@Test
public void resetPasswordExistingUser() {
long userId = 1l;
assertTrue(userService.resetPassword(userId));
}
@Test
public void resetPasswordUserNotExist() {
long userId = 5l;
assertFalse(userService.resetPassword(userId));
}
@Nested
@DisplayName("Test Login Feature")
class LoginFeature {
@Test
void loginUsernamePasswordAreCorrect() {
boolean actual = userService.login("admin", "password123");
assertTrue(actual);
}
@Test
void loginUsernamePasswordAreInCorrect() {
boolean actual = userService.login("admin", "password123456");
assertFalse(actual);
}
@Test
void loginUsernamePasswordAreNulls() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
userService.login(null, null);
});
assertEquals("Username and password must not be null or empty", exception.getMessage());
}
@Test
void loginUsernamePasswordAreEmpty() {
assertThrows(IllegalArgumentException.class, () -> {
userService.login("", "");
});
}
}
@Nested
@DisplayName("Test ChangePassword Feature")
class ChangePasswordFeature {
@Test
void changePasswordUserExistOldPasswordNewPasswordCorrect() {
long userId = 1L; // existed user
assertTrue(userService.changePassword(userId, "password123", "password123456"));
}
@Test
void changePasswordUserNotExist() {
long userId = 999L; // not existed user
assertFalse(userService.changePassword(userId, "password123", "password123456"));
}
@Test
void changePasswordUserExistOldPasswordAndNewPasswordEmpty() {
long userId = 1L; // existed user
assertFalse(userService.changePassword(userId, "", ""));
}
@Test
void changePasswordUserExistOldPasswordEqualNewPassword() {
long userId = 1L; // existed user
assertFalse(userService.changePassword(userId, "password123", "password123"));
}
}
}
Note that we have grouped all test methods related to the Login feature in an inner class, and annotated the class with @Nested annotation:
@Nested
@DisplayName("Test Login Feature")
class LoginFeature {
// Reference above class
}
Note that we have also grouped all test methods related to ChangePassword feature into an inner class and annotated the class with @Nested annotation:
@Nested
@DisplayName("Test ChangePassword Feature")
class ChangePasswordFeature {
//Reference above class
}
@Nested
@DisplayName("Test Login Feature")
class LoginFeature {
// Reference above class
}
@Nested
@DisplayName("Test ChangePassword Feature")
class ChangePasswordFeature {
//Reference above class
}
Rules to add Nested Tests
When we add nested test classes to our test class, we have to follow these rules:- All nested test classes must be non-static inner classes.
- We have to annotate our nested test classes with the @Nested annotation. This annotation ensures that JUnit 5 recognizes our nested test classes.
- There is no limit to the depth of the class hierarchy.
- By default, a nested test class can contain test methods, one @BeforeEach method, and one @AfterEach method.
- Because Java doesn't allow static members in inner classes, the @BeforeAll, and @AfterAll methods don't work by default.
Conclusion
In this post, we have learned JUnit 5 nested tests with examples. The source code for this post is available on GitHub.
JUnit 5 Related Posts
- Overview of JUnit 5
- JUnit 5 Maven Example
- JUnit 5 Standard Test Class Basic Template
- JUnit 5 Annotations with Examples
- JUnit 5 Assertions with Examples
- JUnit 5 Nested Tests Example
- JUnit 5 Disabling Tests Examples
- JUnit 5 Display Names Example
- JUnit 5 Repeated Tests with Examples
- JUnit 5 Exception Testing with Example
Comments
Post a Comment
Leave Comment