测试Spy Mapper类时Properties初始化引发NPE的解决方法
解决Mapper类中Properties空指针异常问题
我在编写Credential类的测试代码时遇到了空指针异常(NPE):已经Mock了properties.getHost()的返回值,但Mapper类中调用properties.getHost()时仍出现NPE,且必须使用Spy处理Mapper类(不能Mock)。
测试类代码
@ExtendWith(MockitoExtension.class) public class CredentialServiceTest { @Mock private Properties properties; @Spy private Mapper mapper = Mockito.spy(new Mapper(properties)); @Test void testGetCredentials() { when(properties.getHost()).thenReturn("http://localhost:8080"); when(credentialRepository.findByCredentialSenderAndInactiveFalse(credential.getCredentialSender())).thenReturn(credential); CredentialsDTO actualCredentials = credentialService.getCredentials(headers); assertEquals(actualCredentials, credentialsDTO); } }
Mapper类代码
@Component @AllArgsConstructor public class Mapper { private Properties properties; public CredentialsDTO mapperCredentialToDTO(Credential credential) { ModelMapper modelMapper = new ModelMapper(); TypeMap<Credential, CredentialsDTO> propertyMapper = modelMapper.createTypeMap(Credential.class, CredentialsDTO.class); CredentialsRoleDTO credentialRoleDTO = mapperCredentialsToCredentialsRoleDTO(credential); List<CredentialsRoleDTO> credentialsRoleDTO = Arrays.asList(credentialRoleDTO); propertyMapper.addMappings(mapper -> mapper.map(src -> credentialsRoleDTO, CredentialsDTO::setRoles)); propertyMapper.addMappings(mapper -> mapper.map(src -> credential.getCredentialSender(), CredentialsDTO::setToken)); propertyMapper.addMappings(mapper -> mapper.map(src -> properties.getHost().concat(URLEnum.VERSIONS.getUrl()), CredentialsDTO::setUrl)); return modelMapper.map(credential, CredentialsDTO.class); } }
Properties类代码
@ConfigurationProperties(prefix = "myapp") @Component @ConstructorBinding @Data public class Properties { private String host; }
问题原因
问题确实出在初始化环节:使用@Mock标注的properties对象,是在测试类实例化完成后才由Mockito完成初始化的。而手动执行new Mapper(properties)时,properties还未被Mockito初始化,仍是null值,导致Mapper内部的properties成员变量始终为null。
解决方案
方案1:使用@BeforeEach延迟初始化Mapper Spy
将Mapper的初始化移到@BeforeEach方法中,确保Mock的Properties已经完成初始化后再创建Spy实例:
@ExtendWith(MockitoExtension.class) public class CredentialServiceTest { @Mock private Properties properties; @Mock private CredentialRepository credentialRepository; private Mapper mapper; @InjectMocks private CredentialService credentialService; @BeforeEach void setUp() { // 先Mock Properties的返回值 when(properties.getHost()).thenReturn("http://localhost:8080"); // 再创建Mapper的Spy实例,此时properties已初始化 mapper = Mockito.spy(new Mapper(properties)); // 手动将Spy的Mapper注入到Service credentialService.setMapper(mapper); } @Test void testGetCredentials() { when(credentialRepository.findByCredentialSenderAndInactiveFalse(credential.getCredentialSender())).thenReturn(credential); CredentialsDTO actualCredentials = credentialService.getCredentials(headers); assertEquals(actualCredentials, credentialsDTO); } }
方案2:让Mockito自动注入Spy的依赖
如果测试类使用@InjectMocks管理CredentialService,可以去掉手动new Mapper的代码,让Mockito自动为Spy注入Properties:
@ExtendWith(MockitoExtension.class) public class CredentialServiceTest { @Mock private Properties properties; @Mock private CredentialRepository credentialRepository; @Spy private Mapper mapper; @InjectMocks private CredentialService credentialService; @BeforeEach void setUp() { when(properties.getHost()).thenReturn("http://localhost:8080"); } @Test void testGetCredentials() { when(credentialRepository.findByCredentialSenderAndInactiveFalse(credential.getCredentialSender())).thenReturn(credential); CredentialsDTO actualCredentials = credentialService.getCredentials(headers); assertEquals(actualCredentials, credentialsDTO); } }
这种方式依赖Mockito的构造器注入能力,由于Mapper使用了@AllArgsConstructor,Mockito会自动匹配构造器参数完成依赖注入。
内容的提问来源于stack exchange,提问作者Serdar
相关产品推荐
相关产品推荐

