Nested test class java

Java Guides

JUnit Jupiter @Nested annotation can be used to mark a nested class to be included in the test cases. When JUnit tests are executed, Nested classes are not scanned for test methods. We can explicitly mark them to be scanned for test cases using @Nested annotation.

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:

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) < ListexistingUsers = new ArrayList<>(Arrays.asList(1L, 2L, 3L)); if (existingUsers.contains(userId)) < return true; >return false; > public boolean logout(long userId) < ListexistingUsers = 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.

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 >

Rules to add Nested Tests

  1. All nested test classes must be non-static inner classes.
  2. We have to annotate our nested test classes with the @Nested annotation. This annotation ensures that JUnit 5 recognizes our nested test classes.
  3. There is no limit to the depth of the class hierarchy.
  4. By default, a nested test class can contain test methods, one @BeforeEach method, and one @AfterEach method.
  5. Because Java doesn’t allow static members in inner classes, the @BeforeAll, and @AfterAll methods don’t work by default.
Читайте также:  Delegate function in javascript

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 Tutorial: Writing Nested Tests

The previous part of this tutorial described how we can use setup and teardown methods, and add test methods to our test classes. Also, we wrote a simple test class and added all setup and teardown methods to the created class.

The source code of our test class looks as follows:

import org.junit.jupiter.api.*; @DisplayName("JUnit 5 Nested Example") class JUnit5NestedExampleTest < @BeforeAll static void beforeAll() < System.out.println("Before all test methods"); >@BeforeEach void beforeEach() < System.out.println("Before each test method"); >@AfterEach void afterEach() < System.out.println("After each test method"); >@AfterAll static void afterAll() < System.out.println("After all test methods"); >>

Next, we will add nested setup, teardown, and test methods to our test class.

Writing Nested Tests

When we write nested tests with JUnit 5, we have to create a nested test class hierarchy that contains our setup, teardown, and test methods. 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 for 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.
  • We should annotate our nested test classes with the @DisplayName annotation because it allows us to replace technical names with a sentence that describes the purpose of the nested test class.
  • If you want to use the @BeforeAll and @AfterAll methods in nested test classes, you should take a look at the JUnit 5 User Guide.
Читайте также:  Php exec service restart

Let’s add a few inner classes to our test class. The idea of this exercise is to demonstrate the invocation order of setup, teardown, and test methods. We can add the required inner classes to our test class by following these steps:

First, we have to add a new inner class called A to our test class and annotate the inner class with the @Nested annotation. After we have created the A class, we have to add one setup, teardown, and test method to the created inner class.

After we have added this inner class to the JUnit5NestedExampleTest class, the source code of our test class looks as follows:

import org.junit.jupiter.api.*; @DisplayName("JUnit 5 Nested Example") class JUnit5NestedExampleTest < @BeforeAll static void beforeAll() < System.out.println("Before all test methods"); >@BeforeEach void beforeEach() < System.out.println("Before each test method"); >@AfterEach void afterEach() < System.out.println("After each test method"); >@AfterAll static void afterAll() < System.out.println("After all test methods"); >@Nested @DisplayName("Tests for the method A") class A < @BeforeEach void beforeEach() < System.out.println("Before each test method of the A class"); >@AfterEach void afterEach() < System.out.println("After each test method of the A class"); >@Test @DisplayName("Example test for method A") void sampleTestForMethodA() < System.out.println("Example test for method A"); >> >

Second, we have to add a new inner class called WhenX to the A class and annotate the inner class with the @Nested annotation. After we have created the WhenX class, we have to add one setup, teardown, and test method to the created inner class.

After we have added this inner class to the A class, the source code of our test class looks as follows:

import org.junit.jupiter.api.*; @DisplayName("JUnit 5 Nested Example") class JUnit5NestedExampleTest < @BeforeAll static void beforeAll() < System.out.println("Before all test methods"); >@BeforeEach void beforeEach() < System.out.println("Before each test method"); >@AfterEach void afterEach() < System.out.println("After each test method"); >@AfterAll static void afterAll() < System.out.println("After all test methods"); >@Nested @DisplayName("Tests for the method A") class A < @BeforeEach void beforeEach() < System.out.println("Before each test method of the A class"); >@AfterEach void afterEach() < System.out.println("After each test method of the A class"); >@Test @DisplayName("Example test for method A") void sampleTestForMethodA() < System.out.println("Example test for method A"); >@Nested @DisplayName("When X is true") class WhenX < @BeforeEach void beforeEach() < System.out.println("Before each test method of the WhenX class"); >@AfterEach void afterEach() < System.out.println("After each test method of the WhenX class"); >@Test @DisplayName("Example test for method A when X is true") void sampleTestForMethodAWhenX() < System.out.println("Example test for method A when X is true"); >> > >
  • The «root» test class contains all test methods of the system under test. Typically I name this class by appending the string: Test to a string that identifies the system under test.
    • An inner class that contains the all tests of a feature or a method. I use the name of the tested feature or method as the name of this inner class.
      • The inner classes which which verify that the system under test is working as expected when a specific condition is true. I name these inner classes by prepending the string: When to a string that describes the condition. For example, if we are writing tests for a finder method, we could have two inner classes: WhenXIsFound and WhenXIsNotFound .

      Additional Reading:

      We have now written a test class that contains nested tests. Let’s see what happens when we run our tests.

      Running Our Tests

      When we run our tests, we should see the following output:

      Before all test methods Before each test method Before each test method of the A class Example test for method A After each test method of the A class After each test method Before each test method Before each test method of the A class Before each test method of the WhenX class Example test for method A when X is true After each test method of the WhenX class After each test method of the A class After each test method After all test methods

      In other words, JUnit 5 invokes the setup and teardown methods by following the context hierarchy of the invoked test method. This means that we can eliminate duplicate code by putting our code to the correct place.

      We have now written a test class that contains nested setup, teardown, and test methods. Also, we understand the invocation order of these methods. Let’s summarize what we learned from this blog post.

      If you want to get access to up-to-date material which is essentially a better version of my JUnit 5 tutorial, you should take a look at my Introduction to JUnit 5 course.

      Summary

      This blog post has taught us five things:

      • All nested test classes must be non-static inner classes.
      • All nested test classes must be annotated with the @Nested annotation.
      • The depth of the test class hierarchy is not limited in any way.
      • A nested test class can contain test methods, one @BeforeEach method, and one @AfterEach method.
      • By default, we cannot add the @BeforeAll and @AfterAll methods to a nested test class.

      Nested tests are very useful because our test cases are often not a list, but form a tree. This leads to maintenance issues and duplication when we try to fit that tree in a list of methods annotated with @Test 🙂 One more thing — I have studied JUnit 5 Guide and totally missed the @TestInstance(Lifecycle.PER_CLASS) as a way to make @BeforeAll/@AfterAll possible on non-static methods. Of course that comes with other limitations, but still — great tip!

      Источник

Оцените статью