问题
I'm receiving an annoying NPE when I run a Unit Test for a service layer class. This class use an autogenerated mapper from MapStruct, which inside use another mapper (see in the Mapper annotation the "uses" attribute):
Mapper(componentModel = "spring", uses = {UserMapper.class})
public interface CandidateMapper extends EntityMapper<CandidateDTO, Candidate> {
@Mapping(target = "createdBy", ignore = true)
@Mapping(target = "createdDate", ignore = true)
@Mapping(target = "lastModifiedBy", ignore = true)
@Mapping(target = "lastModifiedDate", ignore = true)
@Mapping(target = "applications", ignore = true)
Candidate toEntity(CandidateDTO candidateDTO);
default Candidate fromId(Long id) {
if (id == null) {
return null;
}
Candidate candidate = new Candidate();
candidate.setId(id);
return candidate;
}
}
My UnitTest is:
@RunWith(SpringRunner.class)
public class CandidateServiceTest {
private CandidateService candidateService;
@MockBean
private UserRepository userRepository;
@MockBean
CandidateRepository candidateRepository;
@MockBean
UserDetailsService userDetailsService;
CandidateMapper candidateMapper = Mappers.getMapper(CandidateMapper.class);
UserMapper userMapper = Mappers.getMapper(UserMapper.class);
@Before
public void init() {
this.candidateService = new CandidateService(candidateRepository,
candidateMapper, userDetailsService, userMapper);
}
@Test
@WithMockUser(authorities = RolesConstants.ADMIN)
public void givenUser_whenGetCandidateOrCreateForLogin_create() {
// Pre-conditions
...
// Mocking data
...
// Given
given(userRepository.findOneByLogin(eq(login)))
.willReturn(Optional.of(user));
given(candidateRepository.findOneByUserLogin(eq(login)))
.willReturn(Option.of(candidate));
// When
CandidateDTO candidateDTO = candidateService.getCandidateOrCreateForLogin(login);
// Then
...
}
The NPE is raised by this line:
candidateDTO.setUser( userMapper.toDto( candidate.getUser() ) );
in the CandidateMapperImpl because the userMapperImpl instance (variable name userMapper) inside the candidateMapperImpl is null.
This does not happen when I launch the application with spring-boot:run but only with the UnitTest
Any ideas or suggest would be appreciated. Let me know if you'd need more infos or details or if I missed something important.
Thanks
EDIT:
I fixed the issues annotating the Mapper with @Autowired and using this class annotation:
@SpringBootTest(classes = {CandidateMapperImpl.class, UserMapperImpl.class, UserRolesMapperImpl.class,
CandidateMapper.class, UserMapper.class, UserRolesMapper.class})
To generalize:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {SubMapperImpl.class, SubMapper.class,
OtherSubMapper.class, OtherSubMapperImpl.class...})
public class ServiceTest {
@Autowired
Mapper mapper;
...
}
回答1:
It seems, based on your test annotated with @RunWith(SpringRunner.class)
(which is for adding spring context to your test) that you don't really want to be mocking your nested mappers, correct? If you want spring context, just autowire your mapper with all its nested mappers as well. See: https://stackoverflow.com/a/48503708/1098564
Below (with mockito...not as familiar with MockBean yet), would give you a 'fixture' (i.e. CandidateService) with all the nested mappers injected (via @Autowired) and the rest of the dependencies are mocked out. There's probably a cleaner way with MockBean but don't currently have time to test it all out.
@RunWith(SpringRunner.class)
@SpringBootTest
public class CandidateServiceTest {
@Autowired
@InjectMocks
private CandidateService fixture;
@Mock
private UserRepository userRepository;
@Mock
private CandidateRepository candidateRepository;
@Mock
private UserDetailsService userDetailsService;
@Autowired
private CandidateMapper candidateMapper;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
//....tests
}
来源:https://stackoverflow.com/questions/53155556/junit-how-to-mock-mapstruct-nested-mapper