slim slim - 2 months ago 8
Java Question

mockito : mock method call with parameters by reflection

I'm using mockito and developping with java6 and spring.

I'm working on a test API for some developpers and I propose a few methods for mocking objects and methods (it's a legacy code...).
Now, I want to replace all this things by mockito but I always propose a test API. So, I developped some methods using mockito.

I have an old method with two parameters (String). A first parameter is a mocked service id and its method with parameters. And the second parameter is the returned Object.
Example :

mockReturnObject("myServiceId.myMethod(String, Integer)", myReturnedObject);


Now, I want to use mock, when and thenReturn mockito methods, and I don't see how...
Perhaps with reflection but with "when" method it's impossible because mockito need the effective method.
How can I do that ? thanks.

Answer

This is a bad idea: you're trying to reimplement some of the systems Mockito already provides while losing out on many of the features Mockito offers. However, there is a way to make this work, with some difficulty. The key is to write a custom Answer, make it the default answer for the mock, and then compare your object, method name, and method parameter types using InvocationOnMock.

public class ReflectiveMockAnswer implements Answer<Object> {
  @Override public Object answer(InvocationOnMock invocation) {
    // Assume you've successfully parsed each String into a StubbedResponse, with
    // Object target, String method, String[] argTypes, and Object returnValue.
    // A Set would beat a for-loop here, should you need to optimize.
    for (StubbedResponse stubbedResponse : allStubbedResponses) {
      if (stubbedResponse.target == invocation.getMock()
          && stubbedResponse.method.equals(invocation.getMethod().getName())
          && stringArraysEqual(stubbedResponse.argTypes,
              typeNamesFrom(invocation.getMethod().getParameterTypes())) {
        return stubbedResponse.returnValue;
      }
    }
    throw new RuntimeException("Unstubbed method called.");
  }
}

// Later...
Object yourMockObject = Mockito.mock(classToMock, new ReflectiveMockAnswer());

At that point, you've implemented a simplified version of Mockito within and based on the full version of Mockito. You'll also need to:

  • Parse the string into a StubbedResponse, probably with regular expressions
  • Identify the field in your bean-under-test by name
  • Replace that field with a mock of the appropriate class, created as above, before the bean-under-test has a chance to interact with it

...and acknowledge that this solution doesn't handle:

  • Verification
  • Any sort of argument matching, including basic "equals" matching
  • Name collisions in parameter types (com.foo.SomeClass vs com.bar.SomeClass)
  • Repeated calls (thenReturn(1, 2, 3).thenThrow(new RuntimeException()))

...and cannot handle:

  • Code search tools: you can only tell which methods are mocked other than by searching for strings, not with tools like "Find references" in Eclipse the way Mockito can
  • Compile-time checking and automated refactoring tools: your tests would break at runtime if field names, method names, or parameters change; Mockito doesn't have that problem
  • Final methods: Mockito can't, so you can't either

Unless this is a "straw man" or very temporary solution, I recommend strongly to just introduce Mockito directly into your test cases, one test at a time.