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

Spring Boot JUnit测试Mock HashMap抛出异常的解决方法咨询

解决Spring Boot JUnit测试中静态Map空指针与Mockito方法调用异常问题

问题根源

  1. 空指针异常原因:业务类中的MAP是静态字段,依赖@PostConstruct注解的init()方法初始化。如果JUnit测试未启动Spring容器,也未手动触发初始化逻辑,MAP会处于null状态,调用getOrDefault()时抛出空指针。
  2. MissingMethodInvocationException原因:测试代码中的MAP是手动new的普通HashMap实例,并非Mockito创建的mock对象。Mockito的when()方法仅支持拦截mock对象的方法调用,直接对普通对象使用会触发该异常。

解决方案

方案1:手动初始化静态Map(无需重构业务代码)

在测试类中通过反射或直接调用初始化方法,为业务类的静态MAP赋值:

@BeforeClass
public static void setupClass() {
    // 方式1:直接调用业务类的init方法(需确保init是public访问权限)
    YourService serviceInstance = new YourService();
    serviceInstance.init();

    // 方式2:通过反射修改静态字段(适用于init方法为private的场景)
    try {
        Field mapField = YourService.class.getDeclaredField("MAP");
        mapField.setAccessible(true);
        Map<String, String> testMap = new HashMap<>();
        testMap.put("client", "client-url");
        testMap.put("admin", "admin-url");
        mapField.set(null, Collections.unmodifiableMap(testMap));
    } catch (NoSuchFieldException | IllegalAccessException e) {
        e.printStackTrace();
    }
}

@Test
public void testGetResponseForClient() {
    RequestDTO request = new RequestDTO();
    String productType = "client";
    String expectedUrl = "client-url";
    CustomResponse expectedResponse = new CustomResponse(200, "Success", new Object());

    // 仅需mock RestTemplate的postForObject方法,无需处理MAP
    when(restTemplate.postForObject(eq(expectedUrl), any(RequestDTO.class), eq(CustomResponse.class)))
            .thenReturn(expectedResponse);

    CustomResponse response = service.getResponse(request, productType);

    assertEquals(expectedResponse.getCode(), response.getCode());
    verify(restTemplate, times(1)).postForObject(expectedUrl, request, CustomResponse.class);
}

方案2:重构业务代码,采用依赖注入(推荐,符合Spring最佳实践)

将静态MAP改为可注入的Bean,避免静态字段带来的测试不便:

重构后的业务代码

@Service
public class YourService {
    private final Map<String, String> urlMap;
    private final RestTemplate restTemplate;

    // 构造器注入配置的Map和RestTemplate
    public YourService(
            @Value("#{{'client':'CLIENT_URL', 'admin':'ADMIN_URL'}}") Map<String, String> urlMap,
            RestTemplate restTemplate) {
        this.urlMap = Collections.unmodifiableMap(urlMap);
        this.restTemplate = restTemplate;
    }

    public CustomResponse getResponse(RequestDTO request, String productType) {
        String url = "client".equalsIgnoreCase(productType)
                ? urlMap.getOrDefault(productType, "CLIENT_URL")
                : urlMap.getOrDefault(productType, "ADMIN_URL");
        return restTemplate.postForObject(url, request, CustomResponse.class);
    }
}

对应的测试代码

@ExtendWith(MockitoExtension.class)
public class YourServiceTest {
    @InjectMocks
    private YourService service;

    @Mock
    private RestTemplate restTemplate;

    @Mock
    private Map<String, String> urlMap;

    @Test
    public void testGetResponseForClient() {
        RequestDTO request = new RequestDTO();
        String productType = "client";
        String expectedUrl = "client-url";
        CustomResponse expectedResponse = new CustomResponse(200, "Success", new Object());

        // 拦截mock Map的getOrDefault方法
        when(urlMap.getOrDefault(eq(productType), anyString())).thenReturn(expectedUrl);
        when(restTemplate.postForObject(eq(expectedUrl), any(RequestDTO.class), eq(CustomResponse.class)))
                .thenReturn(expectedResponse);

        CustomResponse response = service.getResponse(request, productType);

        assertEquals(expectedResponse.getCode(), response.getCode());
        verify(restTemplate, times(1)).postForObject(expectedUrl, request, CustomResponse.class);
    }
}

关键注意点

  • 切勿对普通Java对象使用Mockito的when()方法,仅能用于Mockito通过mock()、@Mock等方式创建的mock实例。
  • 静态字段会增加测试复杂度,优先采用依赖注入的方式管理配置类或映射关系。

内容的提问来源于stack exchange,提问作者jacksondel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 17:22:03