Verify that all getter methods are called

后端 未结 2 1157
死守一世寂寞
死守一世寂寞 2021-01-02 08:27

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

相关标签:
2条回答
  • 2021-01-02 08:33

    I can think of two solutions for your problem:

    1. Generate the Builder code programmatically, so you don't need to run tests. Java code is generated by a program and never edited by a user. Test the generator instead. Use a text template and build definitions from a serialized domain model or directly from Java compiled classes (you'll need a separate module dependent on the bean's one)

    2. Write your tests against a proxy library. The problem is that regular proxies can only implement interfaces, not regular classes, and it's very cumbersome to have interfaces for Javabeans. If you choose this route, I'd go with Javassist. I coded a runnable sample and put it on GitHub. The test cases use a proxy factory to instantiate beans (instead of using new)

    public class CountingCallsProxyFactory {
    
        public <T> T proxy(Class<T> classToProxy) {
            ProxyFactory factory = new ProxyFactory();
            factory.setSuperclass(classToProxy);
            Class clazz = factory.createClass();
            T instance =  (T) clazz.newInstance();
            ProxyObject proxy = (ProxyObject) instance;
            MethodCallCounter handler = new MethodCallCounter();
            proxy.setHandler(handler);
            return instance;
        }
    
        public void verifyAllGettersCalled(Object bean) {
            // Query the counter against the properties in the bean
        }
    }
    

    The counter is kept inside the class MethodCallCounter

    0 讨论(0)
  • 2021-01-02 08:58

    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.

    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();
    }
    

    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.

    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.

    0 讨论(0)
提交回复
热议问题