Tests in Spring Boot with JUnit 5 and Mockito

In this article, I will configure the tests in Spring Boot with JUnit5 and Mockito. I will also write some unit tests with JUnit 5 and Mockito.

Content:

  • Dependencies
  • Unit tests
  • Mocks and Spies

More details can be found in the following video.

All the code is available in this repository.

Dependencies

The topic is too large to be in a single article, so, I split it in two articles. This one will contain the configuration and the tests in the services. And the second part will contain the integration tests on the controllers and the database. I will start preparing the project to accept JUnit 5 tests, Sprint Test and Mockito. For that, i will add the dependencies in the pom.xml.

     <properties>
        ...
        <junit.version>5.6.0</junit.version>
     <properties>

     <dependencies>
        ...
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>junit</groupId>
                    <artifactId>junit</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>3.3.3</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-junit-jupiter</artifactId>
            <version>3.3.3</version>
            <scope>test</scope>
        </dependency>
     </dependencies>

For the spring-boot-starter-test, i must add some exclusions to avoid conflicts with JUnit5.

Unit tests

I will start by adding tests on the easiest part: the mappers.

public class UserMapperTest {

    private static UserMapper mapper;

    @BeforeAll
    public static void setUp() {
        mapper = new UserMapperImpl();
    }

    @Test
    void testUserMapper() {
        // given
        User user = User.builder()
                .id(1L)
                .firstName("first")
                .lastName("last")
                .login("login")
                .password("pass")
                .createdDate(LocalDateTime.now())
                .build();

        // when
        UserDto userDto = mapper.toUserDto(user);

        // then
        assertAll(
                () -> {
                    assertEquals(user.getFirstName(), userDto.getFirstName());
                    assertEquals(user.getLastName(), userDto.getLastName());
                }
        );
    }
}

The annotation @BeforeAll means that the static method setUp will be executed before all the tests. It will initialize the mapper before starting the tests. This must be done before running the first test.

I use to split the test in three parts: given, when and then. In the given part, I initialize the values, the input parameters and the state of the system. In the when part, I call the methods to be tested. And in the then part, i check the results, I add the assertions.

Let’s see another mapper: the message mapper, which has a dependency to the UserMapper.

This time, I will use the @BeforeAll method to inject the dependency, to inject the user mapper, inside the message mapper. For that I will use the reflection.

public class MessageMapperTest {

    private static MessageMapper messageMapper = new MessageMapperImpl();
    private static UserMapper userMapper = new UserMapperImpl();

    @BeforeAll
    public static void setUp() {
        ReflectionTestUtils.setField(messageMapper, "userMapper", userMapper);
    }

    @Test
    void testMapMessageDto() {
        // given
        MessageDto messageDto = MessageDto.builder()
                .content("content")
                .createdDate(LocalDateTime.now())
                .build();

        // when
        Message message = messageMapper.messageDtoToMessage(messageDto);

        // then
        assertEquals(messageDto.getContent(), message.getContent());
    }
}

Mocks and Spies

Ok, let’s now continue with the next level, let’s continue with the services. The difference now is that I have spring which injects me services or repositories inside the service I want to test. I will work with the UserService which has the UserRepository, a PasswordEncoder and a mapper injected by Spring.

@ExtendWith(MockitoExtension.class)
public class UserServiceTest {

    @InjectMocks
    private UserService userService;

    @Mock
    private UserRepository userRepository;

    @Mock
    private PasswordEncoder passwordEncoder;

    @Spy
    private UserMapper userMapper = new UserMapperImpl();

    @Test
    void testProfileData() {
        // given
        Optional<User> user = Optional.of(User.builder()
                .id(10L)
                .firstName("first")
                .lastName("last")
                .login("login")
                .password("pass")
                .createdDate(LocalDateTime.now())
                .build()
        );
        Mockito.when(userRepository.findById(10L)).thenReturn(user);

        // when
        ProfileDto profile = userService.getProfile(10L);

        // then
        verify(userRepository).findById(10L);
        assertAll(() -> {
            assertEquals("first", profile.getUserDto().getFirstName());
            assertEquals("last", profile.getUserDto().getLastName());
        });
    }
}

The first thing I will need to add is the Mockito extension. Before JUnit 5, it was called runners. This will help me to inject mocks or spies in the service.

The variable annotated by @InjectMocks is the one which receives all the mocks and spies. The mocks are services which are empty, which are dummy. They have no behavior, I must specify each action for them. And the spies are services which will be injected but maintain their original behavior. And I can also check, I can also verify the calls they receive.

This way, I specify the behavior of the repository when the input parameter is 10. And I can verify that a mock, or spy, was called with the given arguments.

I will continue with integration tests with controllers and against a database in another article.

References

All the code is available in this repository.


Never Miss Another Tech Innovation

Concrete insights and actionable resources delivered straight to your inbox to boost your developer career.

My New ebook, Best Practices To Create A Backend With Spring Boot 3, is available now.

Best practices to create a backend with Spring Boot 3

Leave a comment

Discover more from The Dev World - Sergio Lema

Subscribe now to keep reading and get access to the full archive.

Continue reading