在 Mockito 中使用泛型处理匿名 class
Handle anonymous class with Generics in Mockito
我正在尝试使用 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;
}
我把测试方法写成-
@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);
}
当我运行这个测试我总是得到它失败说-
Wanted but not invoked:
jWTGenerator.createJWT(
{testStr=java.lang.Object@24bdb479}
);
看起来存根不起作用,因为我总是得到这一行的 "null" -
Map<String, Object> map = mapper.readValue(json, new TypeReference<Map<String, Object>>() {
}); // Here passing TypeReference annonymously
是不是因为TypeReference的匿名实例化class?
是的,是因为匿名内class。具体来说,您告诉 PowerMockito 替换对 new TypeReference
:
的调用
PowerMockito.whenNew(TypeReference.class).withNoArguments()
.thenReturn(mockTypeReference);
但您实际创建的是一个扩展 TypeReference 的匿名 class,而不是 TypeReference 本身:
Map<String, Object> map = mapper.readValue(
json, new TypeReference<Map<String, Object>>() {});
这对您来说尤其棘手。我在这里的正常建议是“" like TypeReference, because it's a no-dependency token/value/data object that works heavily on reflection, but it also doesn't support equals
the way its cousins in Guice and Guava 做;与 Guice 和 Guava 不同,您不能在测试中创建自己的真实 TypeReference 并与 Mockito 的 eq
.
匹配
您仍然不应该模拟 TypeReference,但您还需要调整针对它的断言方式:
- 如果 Jackson 允许,将匿名 TypeReference subclass 提取到命名等效项,然后使用
isA
检查其类型。
- 将 TypeReference 提取到可见常量并检查其引用相等性。
- 使用
Captor
并使用 getType
检查 TypeReference 的通用类型。
- 创建一个使用
getType
的 ArgumentMatcher
实现并使用 argThat
使用它。
- 切换到
ArgumentMatchers.any()
或ArgumentMatchers.<TypeReference<Map<String, Object>>>any()
,以前在Matchers
和Mockito
界面上。该值无论如何都不太可能改变,因此从实用的角度来看,与说服 PowerMock 以其他方式忽略检查相比,您的系统和测试可能更具可读性和健壮性。
- 理想情况下,尽可能使用真正的依赖项,并只检查功能是否正常工作,而不是检查您是否以您的实施方式恰好与正确的协作者进行交互。无论如何,一个工作函数就是你所追求的,对吧?
我正在尝试使用 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;
}
我把测试方法写成-
@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);
}
当我运行这个测试我总是得到它失败说-
Wanted but not invoked:
jWTGenerator.createJWT(
{testStr=java.lang.Object@24bdb479}
);
看起来存根不起作用,因为我总是得到这一行的 "null" -
Map<String, Object> map = mapper.readValue(json, new TypeReference<Map<String, Object>>() {
}); // Here passing TypeReference annonymously
是不是因为TypeReference的匿名实例化class?
是的,是因为匿名内class。具体来说,您告诉 PowerMockito 替换对 new TypeReference
:
PowerMockito.whenNew(TypeReference.class).withNoArguments()
.thenReturn(mockTypeReference);
但您实际创建的是一个扩展 TypeReference 的匿名 class,而不是 TypeReference 本身:
Map<String, Object> map = mapper.readValue(
json, new TypeReference<Map<String, Object>>() {});
这对您来说尤其棘手。我在这里的正常建议是“equals
the way its cousins in Guice and Guava 做;与 Guice 和 Guava 不同,您不能在测试中创建自己的真实 TypeReference 并与 Mockito 的 eq
.
您仍然不应该模拟 TypeReference,但您还需要调整针对它的断言方式:
- 如果 Jackson 允许,将匿名 TypeReference subclass 提取到命名等效项,然后使用
isA
检查其类型。 - 将 TypeReference 提取到可见常量并检查其引用相等性。
- 使用
Captor
并使用getType
检查 TypeReference 的通用类型。 - 创建一个使用
getType
的ArgumentMatcher
实现并使用argThat
使用它。 - 切换到
ArgumentMatchers.any()
或ArgumentMatchers.<TypeReference<Map<String, Object>>>any()
,以前在Matchers
和Mockito
界面上。该值无论如何都不太可能改变,因此从实用的角度来看,与说服 PowerMock 以其他方式忽略检查相比,您的系统和测试可能更具可读性和健壮性。 - 理想情况下,尽可能使用真正的依赖项,并只检查功能是否正常工作,而不是检查您是否以您的实施方式恰好与正确的协作者进行交互。无论如何,一个工作函数就是你所追求的,对吧?