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)
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.