验证是否调用了所有 getter 方法

2022-01-14 00:00:00 unit-testing reflection mockito java junit

我有以下测试,我需要验证是否调用了 Person 类的所有 getter.到目前为止,我已经使用了 mockito 的 verify() 来确保调用每个 getter.有没有办法通过反思来做到这一点?可能会在 Person 类中添加一个新的 getter,但测试会错过它.

I have the following test where I need to verify that all getters of the Person class are being called. So far I have used mockito's verify() to make sure that each getter is called. Is there a way to do that by reflection? It can be the case that a new getter is added to the Person class but the test will miss that.

public class GetterTest {
    class Person{

        private String firstname;
        private String lastname;

        public String getFirstname() {
            return firstname;
        }

        public String getLastname() {
            return lastname;
        }
    }

    @Test
    public void testAllGettersCalled() throws IntrospectionException{
        Person personMock = mock(Person.class);
        personMock.getFirstname();
        personMock.getLastname();

        for(PropertyDescriptor property : Introspector.getBeanInfo(Person.class).getPropertyDescriptors()) {
            verify(personMock, atLeast(1)).getFirstname();
            //**How to verify against any getter method and not just getFirstName()???**
        }
    }
}

推荐答案

一般情况下,不要模拟被测类.如果您的测试是针对 Person 的,那么您永远不会在其中看到 Mockito.mock(Person.class),因为这是一个非常明显的迹象,表明您正在测试模拟框架而不是系统- 正在测试中.

Generally, don't mock the class under test. If your test is for a Person, you shouldn't ever see Mockito.mock(Person.class) in it, as that's a pretty clear sign that you're testing the mocking framework instead of the system-under-test.

相反,您可能想要创建一个 spy(new Person()),它将使用真正的构造函数创建一个真正的 Person 实现,然后将其数据复制到 Mockito 生成的代理.您可以使用 MockingDetails.getInvocations() 反射性地检查是否调用了每个 getter.

Instead, you may want to create a spy(new Person()), which will create a real Person implementation using a real constructor and then copy its data to a Mockito-generated proxy. You can use MockingDetails.getInvocations() to reflectively check that every getter was called.

// This code is untested, but should get the point across. Edits welcome.
// 2016-01-20: Integrated feedback from Georgios Stathis. Thanks Georgios!

@Test
public void callAllGetters() throws Exception {
  Person personSpy = spy(new Person());
  personSpy.getFirstname();
  personSpy.getLastname();

  assertAllGettersCalled(personSpy, Person.class);
}

private static void assertAllGettersCalled(Object spy, Class<?> clazz) {
  BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
  Set<Method> setOfDescriptors = beanInfo.getPropertyDescriptors()
      .stream()
      .map(PropertyDescriptor::getReadMethod)
      .filter(p -> !p.getName().contains("getClass"))
      .collect(Collectors.toSet());
  MockingDetails details = Mockito.mockingDetails(spy);
  Set<Method> setOfTestedMethods = details.getInvocations()
      .stream()
      .map(InvocationOnMock::getMethod)
      .collect(Collectors.toSet());
  setOfDescriptors.removeAll(setOfTestedMethods);
  // The only remaining descriptors are untested.
  assertThat(setOfDescriptors).isEmpty();
}

可能有一种方法可以在 Mockito 生成的间谍上调用 verifyinvoke,但这似乎非常脆弱,并且非常依赖于 Mockito 内部结构.

There might be a way to call verify and invoke on the Mockito-generated spy, but that seems very fragile, and very dependent on Mockito internals.

顺便说一句,测试 bean 样式的 getter 似乎是对时间/精力的一种奇怪使用.一般来说,重点是测试可能会更改或中断的实现.

As an aside, testing bean-style getters seems like an odd use of time/effort. In general focus on testing implementations that are likely to change or break.

相关文章