在 Mockito 中使用泛型处理匿名类

Sau*_*abh 3 java unit-testing mockito powermockito

我正在尝试使用 Powermockito 为以下方法编写单元测试 -

public String getGenerator(String json) throws IOException {
    String jwt = "";
    ObjectMapper mapper = new ObjectMapper();

    // convert JSON string to Map
    Map<String, Object> map = mapper.readValue(json, new TypeReference<Map<String, Object>>() {
    }); // Here passing TypeReference annonymously

    // Create a JWT
    JWTGenerator generator = new JWTGenerator();
    if (map != null && map.size() > 0) {
        jwt = generator.createJWT(map);
    }

    return jwt;
}
Run Code Online (Sandbox Code Playgroud)

我已将测试方法编写为 -

@Test
public void testGetJWTGenerator() throws Exception {
    ObjectMapper mockMapper = PowerMockito.mock(ObjectMapper.class);
    PowerMockito.whenNew(ObjectMapper.class).withNoArguments().thenReturn(mockMapper);

    JWTGenerator mockJWTDecoder = PowerMockito.mock(JWTGenerator.class);
    PowerMockito.whenNew(JWTGenerator.class).withNoArguments().thenReturn(mockJWTDecoder);

    Map<String, Object> anyMap = new HashMap<String, Object>();
    anyMap.put("testStr", new Object());

    TypeReference<Map<String, Object>> mockTypeReference = (TypeReference<Map<String, Object>>) PowerMockito.mock(TypeReference.class);
    PowerMockito.whenNew(TypeReference.class).withNoArguments().thenReturn(mockTypeReference);

    PowerMockito.when(mockMapper.readValue(Mockito.anyString(), Mockito.eq(mockTypeReference))).thenReturn(anyMap);
    PowerMockito.when(mockJWTDecoder.createJWT(anyMap)).thenReturn(Mockito.anyString());
    utilityController = new UtilityController();
    utilityController.getJWTGenerator("{\"someStr\":\"someStr\"}");
    Mockito.verify(mockJWTDecoder, Mockito.times(1)).createJWT(anyMap);
}
Run Code Online (Sandbox Code Playgroud)

当我运行这个测试时,我总是得到它失败说 -

Wanted but not invoked:
jWTGenerator.createJWT(
    {testStr=java.lang.Object@24bdb479}
);
Run Code Online (Sandbox Code Playgroud)

看起来存根不起作用,因为我总是为这一行得到“null” -

Map<String, Object> map = mapper.readValue(json, new TypeReference<Map<String, Object>>() {
        }); // Here passing TypeReference annonymously
Run Code Online (Sandbox Code Playgroud)

是因为 TypeReference 类的匿名实例化吗?

Jef*_*ica 6

是的,这是因为匿名内部类。具体来说,您告诉 PowerMockito 将调用替换为new TypeReference

PowerMockito.whenNew(TypeReference.class).withNoArguments()
    .thenReturn(mockTypeReference);
Run Code Online (Sandbox Code Playgroud)

但是您实际创建的是一个扩展 TypeReference 的匿名类,而不是 TypeReference 本身:

Map<String, Object> map = mapper.readValue(
    json, new TypeReference<Map<String, Object>>() {});
Run Code Online (Sandbox Code Playgroud)

这对你来说尤其棘手。我在这里的一般建议是“不要模拟数据对象”,比如 TypeReference,因为它是一个无依赖的令牌/值/数据对象,在反射方面有很大作用,但它也不支持equals它在 Guice 和 Guava 中的堂兄弟所做的方式; 你不能只是在你的测试中创建你自己真正的 TypeReference 并与eq你班级中的真实类型进行比较。

您仍然不应该模拟 TypeReference,但您还需要调整对它的断言方式:

  • 如果 Jackson 允许,将匿名 TypeReference 子类提取为命名的等效项,然后使用它isA来检查其类型。
  • 将 TypeReference 提取到可见常量,并检查其上的引用相等性。
  • 使用 aCaptor并稍后使用反射检查 TypeReference 的泛型类型。
  • 切换到Mockito.<TypeReference<Map<String, Object>>>any(),这在技术上属于Matchers类,或ArgumentMatchers在 Mockito 的较新版本中。无论如何,该值不太可能改变,因此实际上,与说服 PowerMock 相比,您的系统和测试通过忽略检查可能更具可读性和健壮性。
  • 理想情况下,尽可能使用真正的依赖项,并检查该函数是否有效,而不是按照您的实现方式与正确的合作者进行交互。无论如何,工作功能是您所追求的,对吗?