2

i'm struggling with some weird problem related with Mockito and JUnit 5 with Spring Boot application.

Here is the fragment of my AccountServiceTest class:

@ExtendWith(MockitoExtension.class)
@Import(MapperConfig.class)
class AccountServiceTest {

    @Mock
    private AccountRepository accountRepository;

    @Mock
    private ModelMapper modelMapper;

    @Mock
    private PasswordEncoder passwordEncoder;

    @Mock
    private AuthenticationManager authenticationManager;

    @Mock
    private JwtTokenUtil jwtTokenUtil;

    @InjectMocks
    private AccountService underTest;

    @Test
    void itShouldCreateAccountAndAddItToDatabase() {
        // given
        CreateAccountDto dto = new CreateAccountDto("service.tester@test.com", "service.tester", "12345");

        // when
        Account account = underTest.create(dto);
        Account found = accountRepository.findByUsername(account.getUsername()).orElseThrow();

        // then
        assertEquals(found, account);
    }
    ...
}

Here is the service I want to test:

@Service
public class AccountService implements FieldValueExists {

    @Autowired
    private AccountRepository accountRepository;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private ModelMapper modelMapper;
    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    public Account create(CreateAccountDto dto) {
        Account account = modelMapper.map(dto, Account.class);
        account.setHashedPassword(passwordEncoder.encode(dto.getPassword()));

        accountRepository.save(account);

        return account;
    }
    ...
}

Everything seems to work in the AccountService itself when used in the controller, but in the testing unit the modelMapper.map(...) method returns null object, so it throws an infamous NullPointerException.

Also I've managed to put a Configuration for this mapper in MapperConfig:

@Configuration
public class MapperConfig {

    @Bean
    public ModelMapper modelMapper() {
        ModelMapper modelMapper = new ModelMapper();

        /*
          Account's related mappings
         */
        modelMapper
                .typeMap(CreateAccountDto.class, Account.class);

        modelMapper
                .typeMap(Account.class, AccountDto.class);

        modelMapper
                .typeMap(Account.class, AuthorDto.class);

        /*
          Note's related mappings
         */
        modelMapper
                .typeMap(CreateNoteDto.class, Note.class);

        modelMapper
                .typeMap(Note.class, NoteDto.class);

        modelMapper
                .typeMap(EditNoteDto.class, Note.class)
                .addMappings(mapper -> {
                    mapper.when(ctx -> ctx.getSource() != null).map(EditNoteDto::getTitle, Note::setTitle);
                    mapper.when(ctx -> ctx.getSource() != null).map(EditNoteDto::getContent, Note::setContent);
                });


        return modelMapper;
    }
}

I don't know what is going wrong, since the dependencies in AccountServiceTest are mocked correctly, I think.

TenDan
  • 41
  • 6
  • 2
    It is not enough to just inject the mocks, we have to also configure the mocks. I recommend reading a tutorial on mockito, e.g. [this one from `baeldung.com`](https://www.baeldung.com/mockito-series). – Turing85 Jan 08 '22 at 19:14
  • Do you mean that every mock must define what every method should do in my testing unit? If not, tell me what should I look on the Baeldung page. – TenDan Jan 08 '22 at 19:47
  • 1
    Read it all. Learn the framework. Or do not use it. If we use frameworks without understanding them, chances are, we are not using but misusing them. – Turing85 Jan 08 '22 at 19:50
  • Currently I only want to know the answer for my main question. When I used .when().thenReturn() method combination, learned from the previous documentations and that one sent by you, now it works properly. Is it the solution for my problem? – TenDan Jan 08 '22 at 20:00

2 Answers2

2

Actually, it is required to mock your own implementation to every method participating in service we want to test.

For example using BDDMockito:

given(modelMapper.map(dto, Account.class))
    .willReturn(/*some object we want to return*/)

It is the only thing we need in our Unit Test, because we only want to check the logic of something we're testing.

For testing every part of our service we must write Integration Test.

Yes, it was fault, and I want to thank @Turing85 for pointing me to the solution of the problem.

TenDan
  • 41
  • 6
0

Use @Autowired for ModelMapper instead of @Mock and see if the problem solves

Azadi Yazdani
  • 246
  • 1
  • 9