这是我不得不嘲笑的最奇怪的方法之一。我需要以某种方式将我的单元测试与以下代码进行协调:
protected void sub(Object obj) {
try {
BeanInfo beanInfo = getBeanInfo(obj);
for (PropertyDescriptor pb : beanInfo.getPropertyDescriptors()) {
String fieldType = pd.getPropertyType.getTypeName();
System.out.println(fieldType);
}
} catch (InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
}
它看起来可能是一个简单的单元测试(我将getBeanInfo()移动到一个单独的方法,所以我可以模拟它而不会绊倒Introspector)。但是,每当我到达getTypeName()时,它总是抛出一个InvocationTargetException。有没有办法以某种方式模拟PropertyDescriptor的属性类型?我在stackoverflow上找到了一个解决方案,但它没有多大帮助。
A strange generics edge case with Mockito.when() and generic type inference
以下是我如何模拟BenInfo对象的代码:
@Test
public void testSub() {
ClientViewer cv = mock(ClientViewer.class); // The class that I'm testing.
when(cv.getBeanInfo(mockValue)).thenReturn(mockBeanInfo);
// Rest of the test.
}
mockValue对象只是一个通用对象。 mockBeanInfo对象非常明显。这段代码确实有效。问题在于模仿PropertyDescriptor名称。
这是getBeanInfo():
protected BeanInfo getBeanInfo(Object obj) {
BeanInfo beanInfo = null;
try {
Class cls = obj.getClas();
beanInfo = Introspector.getBeanInfo(cls);
} catch (IntrospectionException e) {
e.printStackTrace();
}
return beanInfo;
}
最后是mockBeanInfo:
@Mock private java.beans.BeanInfo mockBeanInfo;
答案 0 :(得分:1)
Let's talk about what a Java Bean is:
- 所有属性私有(使用getters/setters)
- 公众no-argument constructor
- 实施
醇>Serializable
。
换句话说,Bean只是一种数据结构。它没有任何行为,也没有你想要阻止模拟发生的意外后果。换句话说,你不应该嘲笑BeanInfo
。
但是,您确实希望确保您的类使用BeanInfo对象执行正确的操作。您希望生产代码和测试中的真实 BeanInfo
对象,因为它是一种数据结构。因此,您真正需要的是一种在测试方法中访问这些真实BeanInfo
对象的方法。
注意:您无法避免在此处使用真实的Introspector
,因为您的应用程序需要它提供的数据。
以下是我如何解决您的问题:
重构您的getBeanInfo()
行为以使用单独的类BeanInfoProvider
:
public class SimpleBeanInfoProvider implements BeanInfoProvider {
public BeanInfo getBeanInfo(Object obj) {
BeanInfo beanInfo = null;
try {
Class cls = obj.getClass();
beanInfo = Introspector.getBeanInfo(cls);
} catch (IntrospectionException e) {
e.printStackTrace();
}
return beanInfo;
}
}
可能通过添加构造函数参数将此行为注入ClientViewer
。
private final BeanInfoProvider provider;
public ClientViewer(..., BeanInfoProvider provider) {
// snip
this.provider = provider;
}
更改使用BeanInfo
的方法以使用此BeanInfoProvider
protected void sub(Object obj) {
try {
BeanInfo beanInfo = provider.getBeanInfo(obj);
// snip
实施生成间谍的BeanInfoProvider
,并允许您访问它们。注意:您需要缓存BeanInfo
间谍,以确保在ClientViewer
和测试方法中获得相同的内容。
public class SpyBeanInfoProvider implements BeanInfoProvider {
private final BeanInfoProvider delegate;
private final Map<Class<?>, BeanInfo> spyMap = new HashMap<>();
public SpyBeanInfoProvider(BeanInfoProvider delegate) {
this.delegate = delegate;
}
@Override
public BeanInfo getBeanInfo(Object obj) {
Class<?> klass = obj.getClass();
if(!spyMap.containsKey(klass)) {
BeanInfo info = spy(delegate.getBeanInfo(obj));
spyMap.put(klass, info);
return info;
} else {
return spyMap.get(obj);
}
}
}
将此注入您的测试
private BeanInfoProvider makeBeanInfoProvider() {
return new SpyBeanInfoProvider(new IntrospectorBeanInfoProvider());
}
@Test
public void testSub() {
BeanInfoProvider provider = makeBeanInfoProvider();
ClientViewer viewer = new ClientViewer(makeBeanInfoProvider());
viewer.sub(obj);
BeanInfo spy = provider.getBeanInfo(obj);
// Now do your test
verify(spy).getPropertyDescriptors();
// etc.
}
这将允许您访问生成的BeanInfo
个对象 - 因为它们是真实的数据结构,实现为部分模拟,并且您将不再获得这些InvocationTargetException
。< / p>