Spring Boot JUnit测试Mock HashMap抛出异常的解决方法咨询
解决Spring Boot JUnit测试中静态Map空指针与Mockito方法调用异常问题
问题根源
- 空指针异常原因:业务类中的
MAP是静态字段,依赖@PostConstruct注解的init()方法初始化。如果JUnit测试未启动Spring容器,也未手动触发初始化逻辑,MAP会处于null状态,调用getOrDefault()时抛出空指针。 - 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
相关产品推荐
相关产品推荐

