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
.
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:
18.190.217.253