Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Checking in a unit test whether all methods are delegated

Suppose I have the following class

public abstract class Foo{

  public int bar(){
    //implementation
  }
  public abstract int bar2();
}

and a base class to make it easier to write decorators for this class

public class FooWrapper{
  private final Foo delegate;

  protected FooWrapper( Foo delegate ){
    this.delegate = delegate;
  }
  @Override
  public int bar(){
    return delegate.bar()
  }
  @Override
  public int bar2(){
    return delegate.bar2();
  }
}

The class FooWrapper allows you to write a decorator for Foo where you only override the methods you need.

Now I want to write a test for FooWrapper which checks whether all methods are by default delegated. Of course I can write something like

@Test
public void barShouldBeDelegated(){
  Foo delegate = Mockito.mock( Foo.class );
  FooWrapper wrapper = new FooWrapper( delegate );
  wrapper.bar();
  Mockito.verify( delegate ).bar();
}

But this requires me to add a new test method each time a method is added to Foo. I was hoping to have a test which would fail each time I add a method to Foo which I forget to override and delegate in FooWrapper.

I was trying to use reflection which allows me to call each method, but I do not know how to check whether the method is actually delegated. See the following snippet for the idea I was toying with:

  @Test
  public void testAllMethodsAreDelegated() throws Exception{
    Foo delegate = mock(Foo.class);
    FooWrapper wrapper = new FooWrapper(delegate);

    Class<?> clazz = wrapper.getClass();
    Method[] methods = clazz.getDeclaredMethods();

    for (Method method : methods) {
      Class<?>[] parameterTypes = method.getParameterTypes();
      Object[] arguments = new Object[parameterTypes.length];
      for (int j = 0; j < arguments.length; j++) {
        arguments[j] = Mockito.mock(parameterTypes[j]);
      }
      method.invoke(wrapper, arguments);

      // ?? how to verify whether the delegate is called
      // Mockito.verify( delegate ).??? does not work 
      // as I cannot specify the method by name
      }
    }
  }

Any ideas whether it is possible to write such a test. Note that the only mocking framework I can use is Mockito.

like image 584
Robin Avatar asked Mar 06 '14 13:03

Robin


People also ask

Should I unit test all methods?

The answer to the more general question is yes, you should unit test everything you can. Doing so creates a legacy for later so changes down the road can be done with peace of mind. It ensures that your code works as expected.

What are checked in unit testing?

Unit testing is a software development process in which the smallest testable parts of an application, called units, are individually and independently scrutinized for proper operation. This testing methodology is done during the development process by the software developers and sometimes QA staff.

Can protected methods be unit tested?

So yes, you would test private and protected methods if you felt they needed to be tested for you to answer Yes to the question. Once you've got working code, you need to have a mechanism in place to protect this code from future damage.

What is a test delegate C#?

In C#, delegates are similar to pointers available in C++. It is basically a reference type variable that contains a reference to another method. Further, its reference cannot be changed during the run time. It is available inside System.


1 Answers

This code seems to do the trick. If I add a method to Foo and do not include it in FooWrapper, the test fails.

    FooWrapper wrapper = new FooWrapper(delegate);
    Foo delegate = Mockito.mock(Foo.class);

    // For each method in the Foo class...
    for (Method fooMethod : Foo.class.getDeclaredMethods()) {
        boolean methodCalled = false;

        // Find matching method in wrapper class and call it
        for (Method wrapperMethod : FooWrapper.class.getDeclaredMethods()) {
            if (fooMethod.getName().equals(wrapperMethod.getName())) {

                // Get parameters for method
                Class<?>[] parameterTypes = wrapperMethod.getParameterTypes();
                Object[] arguments = new Object[parameterTypes.length];
                for (int j = 0; j < arguments.length; j++) {
                    arguments[j] = Mockito.mock(parameterTypes[j]);
                }

                // Invoke wrapper method
                wrapperMethod.invoke(wrapper, arguments);

                // Ensure method was called on delegate exactly once with the correct arguments
                fooMethod.invoke(Mockito.verify(delegate, Mockito.times(1)), arguments);

                // Set flag to indicate that this foo method is wrapped properly.
                methodCalled = true;
            }
        }

        assertTrue("Foo method '" + fooMethod.getName() + "' has not been wrapped correctly in Foo wrapper", methodCalled);
    }

The key line here which was missing in your code is

fooMethod.invoke(Mockito.verify(delegate, Mockito.times(1)), arguments);

It might look a little odd, but this works because it invokes things in the same order Mockito expects: first Mockito.verify(delegate) is called (which internally starts the Mockito verification), then the method is invoked. A similar non-reflection invocation would look like Mockito.verify(delegate).foo(). Use this 'why' to help adapt the code to different use cases without breaking how the test does verification.

One word of caution, I would add a check at the beginning of each of your loops which iterate over the result of getDeclaredMethods(). This method returns all methods whether they are public, private, protected etc. Attempting to access an inaccessible method throws an exception. You can use Method.isAccessible() to check for this.

like image 116
Jamie Holdstock Avatar answered Oct 20 '22 22:10

Jamie Holdstock