Understanding the Mockito architecture

Mockito applies the proxy design pattern to create mock objects. For concrete classes, Mockito internally uses CGLib to create proxy stubs. CGLib is used to generate dynamic proxy objects and intercept field access. The following URL talks about CGLib:

https://github.com/cglib/cglib

The following sequence diagram depicts the call sequence. The ClassImposterizer class is a singleton class. This class has a createProxyClass method for generating a source using CGLib. Finally, it uses reflection to create an instance of the proxy class. Method calls are stubbed using the callback API of MethodInterceptor.

Understanding the Mockito architecture

The MethodInterceptor class acts as a Java reflection class, java.lang.reflect.InvocationHandler. Any method call on a mock object (proxy) is handled by a MethodInterceptor instance.

We'll create a custom mocking framework to handle external dependencies. We'll use the Java reflection framework's dynamic proxy object-creation API. The java.lang.reflect.Proxy method provides a Proxy.newProxyInstance(ClassLoader, Class, InvocationHandler) API to create dynamic proxy objects. The InvocationHandler interface has the following signature:

public interface InvocationHandler {

  public abstract Object invoke(Object obj, Method method, Object aobj[])  throws Throwable;
}

All method calls to a proxy object are redirected to the invoke method.

Create a class OurMockito for handling dynamic proxies. The following is the OurMockito class definition. It implements the InvocationHandler interface, provides an implementation of the invoke() method, and provides three static mock methods and two stub methods.

public class OurMockito implements InvocationHandler {
  private static Map<String, Object> stubMap = new HashMap<String, Object>();
  private static Map<String, Exception> excepMap = new HashMap<String, Exception>();

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

    String methodName = method.getName();
    if (Modifier.isFinal(method.getModifiers()) || Modifier.isPrivate(method.getModifiers()) || Modifier.isStatic(method.getModifiers())) {
      throw new RuntimeException("You naughty developer mocking a private, static or final method "+ methodName);
    }

    if (excepMap.containsKey(methodName)) {
      Exception excep = excepMap.get(methodName);
      throw excep;
    }

    if (stubMap.containsKey(methodName)) {
      return stubMap.get(methodName);
    }

    return null;
  }

The mock() method takes a java.lang.Class, creates a proxy object of the class, and passes an instance of OurMockito() as InvocationHandler. The following is the body of the mock() method:

  public static Object mock(Class aClass) {
    Object newProxyInstance = Proxy.newProxyInstance(OurMockito.class.getClassLoader(), new  Class[] { aClass },new OurMockito());
   return newProxyInstance;
  }

The two overloaded stub methods are as follows:

  public static void stub(Object stubOn, String methodName, Object stubbedValue) {
    stubMap.put(methodName, stubbedValue);
  }

  public static void stub(Object stubOn,String methodName, Exception excep) {
    if (excep != null) {
      excepMap.put(methodName, excep);
    }
  }
}

The mock method uses the proxy class to generate a proxy object. The stub(Object stubOn, String methodName, Object stubbedValue) method allows a method call return value to stub. The stub(Object stubOn,String methodName, Exception excep) method allows an exception to be thrown on a method call to check the negative testing path. The stub methods populate two hashmaps for storing the stubbed values/exceptions. The reflection API delegates the method calls (on proxy objects) to InvocationHandler. The invoke method in the OurMockito class handles the method calls. The invoke method looks up the method name in the exception map. If the method was stubbed for throwing an exception, the exception is thrown; otherwise, the method stub map is looked up for returning a stubbed value.

Create an interface to represent an external dependency. The following is the class:

public interface ExternalService {
  public String concat(String arg1, String arg2);
  public void someStrangeOperation(Object obj);
  public int divide(int a, int b);
}

Now create a test class to verify the mocking capability. The following is the class:

public class OurMockTest {

  ExternalService externalService = (ExternalService)OurMockito.mock(ExternalService.class);

  @Test
  public void stubbing_method() throws Exception {
    OurMockito.stub(externalService, "concat", "dummy");
    String returned = externalService.concat(null, null);
    assertEquals("dummy", returned);
  }

  @Test
  public void stubbing_error_conditions() throws Exception {
    OurMockito.stub(externalService, "divide", 0);
    int returned = externalService.divide(0, 0);
    assertEquals(0, returned);
  }

  @Test
  public void stubbing_exception() throws Exception {
    OurMockito.stub(externalService, "someStrangeOperation", new RuntimeException("Just blow this up!"));
    externalService.someStrangeOperation(null);
  }
}

The ExternalService method is mocked using following construct:

ExternalService externalService = (ExternalService)OurMockito.mock(ExternalService.class);

The concat method is stubbed to return a string "dummy", the divide method is stubbed to return a hardcoded integer 0, and the someStrangeOperation method is stubbed to throw a RuntimeException. The following is the JUnit output:

Understanding the Mockito architecture

Note

Note that the third test throws the RuntimeException ("Just blow this up!").

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.190.217.253