1

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

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上找到了一个解决方案,但它并没有太大帮助。

带有 Mockito.when() 和泛型类型推断的奇怪泛型边缘案例

这是我如何模拟 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;
4

1 回答 1

2

让我们谈谈Java Bean是什么

  1. 所有属性都是私有的(使用getters/setters
  2. 公共无参数构造函数
  3. 实现Serializable

换句话说,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.
    }
    

这将允许您访问BeanInfo生成的对象 - 因为它们是真实的数据结构,实现为部分模拟,您将不再获得这些InvocationTargetExceptions。

于 2015-08-05T21:46:07.127 回答