如何从JUnit测试中的模拟对象获取typeName()?

时间:2015-08-05 19:48:50

标签: java junit mocking mockito propertydescriptor

这是我不得不嘲笑的最奇怪的方法之一。我需要以某种方式将我的单元测试与以下代码进行协调:

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;

1 个答案:

答案 0 :(得分:1)

Let's talk about what a Java Bean is

  
      
  1. 所有属性私有(使用getters/setters
  2.   
  3. 公众no-argument constructor
  4.   
  5. 实施Serializable
  6.   

换句话说,Bean只是一种数据结构。它没有任何行为,也没有你想要阻止模拟发生的意外后果。换句话说,你不应该嘲笑BeanInfo

但是,您确实希望确保您的类使用BeanInfo对象执行正确的操作。您希望生产代码和测试中的真实 BeanInfo对象,因为它是一种数据结构。因此,您真正需要的是一种在测试方法中访问这些真实BeanInfo对象的方法。

注意:您无法避免在此处使用真实的Introspector,因为您的应用程序需要它提供的数据。

以下是我如何解决您的问题:

  1. 重构您的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;
      }
    }
    
  2. 可能通过添加构造函数参数将此行为注入ClientViewer

    private final BeanInfoProvider provider;
    
    public ClientViewer(..., BeanInfoProvider provider) {
      // snip
      this.provider = provider;
    }  
    
  3. 更改使用BeanInfo的方法以使用此BeanInfoProvider

    protected void sub(Object obj) {
      try {
        BeanInfo beanInfo = provider.getBeanInfo(obj);
        // snip
    
  4. 实施生成间谍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);
        }
      }
    }
    
  5. 将此注入您的测试

    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.
    }
    
  6. 这将允许您访问生成的BeanInfo个对象 - 因为它们是真实的数据结构,实现为部分模拟,并且您将不再获得这些InvocationTargetException。< / p>