使用@InjectMocks时测试触发NPE,直接实例化则正常问题排查
问题描述
我正在练习RESTful API端点,采用TDD开发获取所有供应商的端点时,用@InjectMocks注入VendorServiceImpl会触发空指针异常(NPE),但直接在setup()里实例化服务(vendorService = new VendorServiceImpl(VendorMapper.INSTANCE, vendorRepository);)就能正常通过测试。怀疑NPE和VendorMapper有关,想通过@InjectMocks让测试通过,相关代码如下:
VendorServiceTest
package guru.springfamework.services; import guru.springfamework.api.v1.mapper.VendorMapper; import guru.springfamework.api.v1.model.VendorDTO; import guru.springfamework.domain.Vendor; import guru.springfamework.repositories.VendorRepository; import org.junit.Before; import org.junit.Test; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.test.web.servlet.MockMvc; import java.util.Arrays; import java.util.List; import static org.junit.Assert.*; import static org.mockito.Mockito.when; public class VendorServiceTest { public static final String NAME = "Tasty"; public static final Long ID = 1L; @Mock VendorMapper vendorMapper; @Mock VendorRepository vendorRepository; @InjectMocks VendorServiceImpl vendorService; //VendorService vendorService; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); //vendorService = new VendorServiceImpl(VendorMapper.INSTANCE, vendorRepository); } @Test public void getAllVendors() { //given List<Vendor> vendors = Arrays.asList(new Vendor(), new Vendor(), new Vendor()); when(vendorRepository.findAll()).thenReturn(vendors); //when List<VendorDTO> vendorDTOList = vendorService.getAllVendors(); //then assertEquals(3, vendorDTOList.size()); } @Test public void findByName() { } }
VendorServiceImpl
package guru.springfamework.services; import guru.springfamework.api.v1.mapper.VendorMapper; import guru.springfamework.api.v1.model.VendorDTO; import guru.springfamework.repositories.VendorRepository; import org.springframework.stereotype.Service; import java.util.List; import java.util.stream.Collectors; @Service public class VendorServiceImpl implements VendorService { private final VendorMapper vendorMapper; private final VendorRepository vendorRepository; public VendorServiceImpl(VendorMapper vendorMapper, VendorRepository vendorRepository) { this.vendorMapper = vendorMapper; this.vendorRepository = vendorRepository; } @Override public List<VendorDTO> getAllVendors() { return vendorRepository .findAll() .stream() .map(vendor -> { VendorDTO vendorDTO = vendorMapper.vendorToVendorDTO(vendor); vendorDTO.setVendorUrl("/api/v1/vendors/" + vendor.getId()); return vendorDTO; }) .collect(Collectors.toList()); } @Override public VendorDTO findByName(String name) { return vendorMapper.vendorToVendorDTO(vendorRepository.findByName(name)); } @Override public VendorDTO getVendorById(Long id) { return vendorMapper.vendorToVendorDTO(vendorRepository.findById(id).orElseThrow(RuntimeException::new)); } }
VendorMapper
package guru.springfamework.api.v1.mapper; import guru.springfamework.api.v1.model.VendorDTO; import guru.springfamework.domain.Vendor; import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; @Mapper public interface VendorMapper { VendorMapper INSTANCE = Mappers.getMapper(VendorMapper.class); VendorDTO vendorToVendorDTO(Vendor vendor); }
问题原因与解决方案
问题根源
你用@Mock注解了VendorMapper,但没有给这个Mock对象的vendorToVendorDTO方法设置返回值。当测试执行时,vendorMapper.vendorToVendorDTO(vendor)会返回null,后续调用vendorDTO.setVendorUrl()就会触发NPE。
而直接实例化服务时用的是VendorMapper.INSTANCE,这是MapStruct生成的真实实现类,它能正常返回非空的VendorDTO对象,所以不会报错。
解决步骤
方案1:给Mock的Mapper设置返回值
在getAllVendors测试方法里,添加对vendorMapper方法的模拟逻辑:
// 先导入静态方法:import static org.mockito.ArgumentMatchers.any; @Test public void getAllVendors() { //given List<Vendor> vendors = Arrays.asList(new Vendor(), new Vendor(), new Vendor()); // 模拟Mapper的转换方法,返回非空的VendorDTO when(vendorMapper.vendorToVendorDTO(any(Vendor.class))).thenReturn(new VendorDTO()); when(vendorRepository.findAll()).thenReturn(vendors); //when List<VendorDTO> vendorDTOList = vendorService.getAllVendors(); //then assertEquals(3, vendorDTOList.size()); }
方案2:使用真实的Mapper实现
如果你的测试需要验证MapStruct的映射逻辑,不应该MockVendorMapper,而是直接使用VendorMapper.INSTANCE,此时只需MockVendorRepository:
// 去掉@Mock VendorMapper @Mock VendorRepository vendorRepository; @InjectMocks VendorServiceImpl vendorService; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); // 手动传入真实的Mapper实例 vendorService = new VendorServiceImpl(VendorMapper.INSTANCE, vendorRepository); }
内容的提问来源于stack exchange,提问作者codeskin
相关产品推荐
相关产品推荐

