1. Unit Testing with Spring boot-reflecing is motoring

Writing good unit tests can be considered a difficult art to master. But the good news is that the mechanisms that support it are easy to learn. This tutorial provides you with these mechanisms and goes through the technical details necessary to write good unit tests, with an emphasis on Spring Boot applications. We’ll look at how to create Spring beans in a testable way, then discuss the use of Mockito and AssertJ, which are included by default in Spring Boot for testing. Note that this article deals only with unit tests. Integration testing, Web layer testing, and persistence layer testing will be discussed in future articles in this series.

 code example

Examples of working code on GitHub are attached to this article.

dependencies

For the unit tests in this tutorial, we will use JUnit Jupiter (JUnit 5), Mockito, and AssertJ. We’ll also include Lombok to reduce some boilerplate code:

dependencies {
    compileOnly('org.projectlombok:lombok')
    testCompile('org.springframework.boot:spring-boot-starter-test')
    testCompile('org. Junit. Jupiter: junit - Jupiter: 5.4.0')
    testCompile('org. Mockito: mockito - junit - Jupiter: 2.23.0')}Copy the code

Mockito and AssertJ were imported automatically using the spring-boot-starter-test dependency, but we had to include Lombok ourselves.

Do not use Spring in unit tests

If you’ve written tests in Spring or Spring Boot before, you might say we don’t need Spring to write unit tests. Why is that? Consider the following “unit” tests that test a single method of the RegisterUseCase class:

@ExtendWith(SpringExtension.class)
@SpringBootTest
class RegisterUseCaseTest {

    @Autowired
    private RegisterUseCase registerUseCase;

    @Test
    void savedUserHasRegistrationDate(a) {
        User user = new User("zaphod"."[email protected]"); User savedUser = registerUseCase.registerUser(user); assertThat(savedUser.getRegistrationDate()).isNotNull(); }}Copy the code

This test took about 4.5 seconds to run on an empty Spring project on my computer. But a good unit test takes milliseconds. Otherwise it impedes the test/code/test process driven by test-driven development (TDD) thinking. But even if we don’t use TDD, waiting too long for testing can ruin our concentration. Executing the above test method actually takes a few milliseconds. The remaining 4.5 seconds are due to @SpringBootrun telling SpringBoot to set the entire SpringBoot application context. So we started the entire application just to auto-assemble the RegisterUseCase instance into our tests. Once the application gets bigger and Spring has to load more and more beans into the application context, it takes longer. So why shouldn’t we use Spring Boot in our unit tests? To be honest, most of this tutorial is about writing unit tests without Spring Boot.

Create testable Spring beans

However, there are a few things you can do to improve the testability of Spring beans.

Field injection is not desirable

Let’s start with a bad example. Consider the following classes:

@Service
public class RegisterUseCase {

    @Autowired
    private UserRepository userRepository;

    public User registerUser(User user) {
        returnuserRepository.save(user); }}Copy the code

This class cannot be unit tested without Spring because it does not provide a way to pass the UserRepository instance. So, we need to write the test as discussed in the previous section and have Spring create a UserRepository instance and inject it into the @Autowired annotated field.

The lesson here is not to use field injection.

Provide constructors

In fact, let’s not use the @autowired annotation at all:

@Service
public class RegisterUseCase {

    private final UserRepository userRepository;

    public RegisterUseCase(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User registerUser(User user) {
        returnuserRepository.save(user); }}Copy the code

This version allows constructor injection by providing a constructor that allows UserRepository instances to be passed in. In unit tests, we can now create such an instance (perhaps the mock instance we’ll discuss later) and pass it to the constructor. Spring automatically uses this constructor to instantiate the RegisterUseCase object when the production application context is created. Note that prior to Spring 5, we needed to add the @Autowired annotation to the constructor so that Spring could find the constructor. Also note that the UserRepository field is now final. This makes sense because the field contents never change during the life of the application. It also helps avoid programming errors, since the compiler will report an error if we forget to initialize the field.

Reduce boilerplate code

Using Lombok’s @requiredargsconstructor annotation, we can make the constructor automatically generate:

@Service
@RequiredArgsConstructor
public class RegisterUseCase {

    private final UserRepository userRepository;

    public User registerUser(User user) {
        user.setRegistrationDate(LocalDateTime.now());
        returnuserRepository.save(user); }}Copy the code

Now we have a very compact class with no boilerplate code that can be easily instantiated in a normal Java test case:

class RegisterUseCaseTest {

    privateUserRepository userRepository = ... ;private RegisterUseCase registerUseCase;

    @BeforeEach
    void initUseCase(a) {
        registerUseCase = new RegisterUseCase(userRepository);
    }

    @Test
    void savedUserHasRegistrationDate(a) {
        User user = new User("zaphod"."[email protected]"); User savedUser = registerUseCase.registerUser(user); assertThat(savedUser.getRegistrationDate()).isNotNull(); }}Copy the code

However, one thing that is missing is how to simulate the UserRepository instance on which our class under test depends, since we don’t want to rely on the real thing, which might need to be connected to a database.

Use Mockito to simulate dependencies

The de facto standard simulation library is Mockito. It provides at least two ways to create mock UserRepository to fill in the gaps in the previous code examples.

Use plain Mockito to simulate dependencies

The first method is to use Mockito programmatically:

private UserRepository userRepository = Mockito.mock(UserRepository.class);
Copy the code

This will create an object that looks like UserRepository from the outside. By default, a method does nothing when it is called, and returns NULL if the method has a return value. Our test will now in assertThat (savedUser getRegistrationDate ()). IsNotNull () impose a NullPointerException failure, Userrepository.save (user) now returns null. So, we must tell Mockito to return something when userRepository.save() is called. We use the static when method to do this:

    @Test
    void savedUserHasRegistrationDate(a) {
        User user = new User("zaphod"."[email protected]");
        when(userRepository.save(any(User.class))).then(returnsFirstArg());
        User savedUser = registerUseCase.registerUser(user);
        assertThat(savedUser.getRegistrationDate()).isNotNull();
    }
Copy the code

This will cause userRepository.save() to return the same user object passed to the method. Mockito has more features to simulate, match parameters, and validate method calls. See the reference documentation for more information.

Use Mockito@MockAnnotate mock dependencies

Another way to create Mock objects is to combine Mockito’s @Mock annotation with JUnit Jupiter’s MockitoExtension:

@ExtendWith(MockitoExtension.class)
class RegisterUseCaseTest {

    @Mock
    private UserRepository userRepository;

    private RegisterUseCase registerUseCase;

    @BeforeEach
    void initUseCase(a) {
        registerUseCase = new RegisterUseCase(userRepository);
    }

    @Test
    void savedUserHasRegistrationDate(a) {
        // ...}}Copy the code

The @mock annotation specifies the fields that Mockito should inject into the Mock object. The @mockitoextension tells Mockito to evaluate the @Mock annotations because JUnit does not do this automatically. As a result, as with manually calling mockito.mock (), choosing which method to use is a matter of taste. Note, however, that our tests are bound to the testing framework by using MockitoExtension. Note that we can also use the @InjectMocks annotation on the registerUseCase field instead of constructing the registerUseCase object manually. Mockito will then create an instance for us using the specified algorithm:

@ExtendWith(MockitoExtension.class)
class RegisterUseCaseTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private RegisterUseCase registerUseCase;

    @Test
    void savedUserHasRegistrationDate(a) {
        // ...}}Copy the code

Create readable assertions using AssertJ

Another library that comes automatically with Spring Boot test support is AssertJ. We have used it above to implement our assertion:

assertThat(savedUser.getRegistrationDate()).isNotNull();
Copy the code

However, wouldn’t it be better to make assertions more readable? Such as:

assertThat(savedUser).hasRegistrationDate();
Copy the code

In many cases, small changes like this will make the test easier to understand. So, let’s create our own custom assertion in the test source folder:

class UserAssert extends AbstractAssert<UserAssert.User> {

    UserAssert(User user) {
        super(user, UserAssert.class);
    }

    static UserAssert assertThat(User actual) {
        return new UserAssert(actual);
    }

    UserAssert hasRegistrationDate(a) {
        isNotNull();
        if (actual.getRegistrationDate() == null) {
            failWithMessage(
                    "Expected user to have a registration date, but it was null"
            );
        }
        return this; }}Copy the code

Now, if we import the assertThat method from the new UserAssert class instead of the AssertJ library, we can use the new, easier to read assertion. Creating custom assertions like this may seem like a lot of work, but it actually takes only a few minutes. I firmly believe that the time invested in creating readable test code is worth it, even if it becomes only slightly more readable later. After all, we only write test code once, and others (including the “future me”) must read, understand, and manipulate the code many times over the life of the software. If it still feels like too much work, check out AssertJ’s assertion generator.

conclusion

There is a reason to start a Spring application in a test, but it is not necessary for normal unit testing. It can even be harmful because of the longer turnaround time. Instead, we should build our Spring beans in a way that makes it easy to write simple unit tests for them. The Spring Boot Test Starter comes with Mockito and AssertJ as Test libraries. Let’s leverage these test libraries to create expressive unit tests! Code samples in their final form can be found on Github.