Search code examples
javaspring-bootjunitmockingmockito

Mockito service dependency null pointer exception


So, let me show my problem. I have Service class:

@Service
@Transactional
public class UserServiceImpl implements UserService {
    private final UserRepository userRepository;
    private final UserMapper userMapper;

    @Autowired
    public UserServiceImpl(UserRepository userRepository, UserMapper userMapper) {
        this.userRepository = userRepository;
        this.userMapper = userMapper;
    }

    @Override
    public UserResponse findById(Integer id) {
        return userMapper.toUserResponse(userRepository.findById(id)
                .orElseThrow(() -> new NoSuchEntityIdException(EntityTypeMessages.USER_MESSAGE, id)));
    }

Note, that userMapper is mapstruct mapper. And i have test class:

@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    @InjectMocks
    private UserServiceImpl userService;
    @Mock
    private UserRepository userRepository;

    @Test
    public void findById_withValidUserId_returnsUser() {
        Integer userId = 1;
        User expectedUser = User.builder().id(userId).build();
        Mockito.when(userRepository.findById(Mockito.argThat(argument -> (argument != null) && (argument >=1)))).
                thenReturn(Optional.of(expectedUser));
        UserResponse userResponse = userService.findById(userId);

        Assertions.assertNotNull(userResponse);
        Assertions.assertEquals(expectedUser.getId(), userResponse.id());
    }
}

I try to check method findById in UserService, but i got java.lang.NullPointerException: Cannot invoke "com.innowise.util.mappers.UserMapper.toUserResponse(com.innowise.domain.User)" because "this.userMapper" is null

In my mind, i expecting, that userMapper real bean autowired properly without it's mocking(I mock only repository, as you can see). But error show that userMapper is null. How to define this mocks properly, because i want to use default userMapper(not mock, becuse i test mappers separately)


Solution

  • What I normally do is:

    @InjectMocks
    private UserServiceImpl userService;
    @Mock
    private UserRepository userRepository;
    @Spy
    private UserMapper userMapper = Mappers.getMapper(UserMapper.class);
    

    This creates a MapStruct spy around the userMapper, which we don't actually need, but which does trick MapStruct into injecting it into the class under test. This gets everything done with only annotations.

    If you don't want that, you can just create the class yourself:

    private UserServiceImpl userService;
    
    @Mock
    private UserRepository userRepository;
    
    @Before
    public void setUp() {
        UserMapper userMapper = Mappers.getMapper(UserMapper.class);
        userService = new UserService(userRepository, userMapper);
    }
    

    Now you've taken over responsibility for creating the test instance of UserService yourself, instead of letting Mockito create it for you with @InjectMocks.