You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

测试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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 04:39:59