Building your own Mock objects with Proxy pattern in Java
Most of us, if not all, have written unit tests for our code using JUnit or comparable tools. In most cases we are in control of what is being tested and we can provide all the inputs that are needed to test the scenario. But then there are cases where there are external factors or classes that cannot be instantiated for tests and we need to find ways to simulate them - the suggested approach is to use mock objects.
There are many frameworks like EasyMock which help create mock objects for classes and interfaces.
How do they mock objects? Turns out it is not magic.
Proxy Pattern
The Proxy pattern is the core of any mock framework. As per Wikipedia, Proxy Pattern is defined as
A proxy, in its most general form, is a class functioning as an interface to something else. The proxy could interface to anything: a network connection, a large object in memory, a file, or some other resource that is expensive or impossible to duplicate.
So when we say that we are interacting with a proxy object, it means that we are working with something that behaves exactly the same way as the object would behave if it was instantiated using the class.
Proxies can be created using many libraries, but we will not get into that - instead we will see how easy it is to create a proxy of any class in simple Java.
Proxy and InvocationHandler
The Proxy class in Java SDK provides the mechanism to create a proxy instance of any interface - it has a newProxyInstance method which takes a class loader, a set of interfaces and a invocation handler to create a proxy. The invocation handler is an object to which all the method calls on the proxy are delegated to.
The InvocationHandler interface has a single method called invoke which gets called with the proxy object, the method to be called and the arguments. This method is supposed to return a value compatible with the method being invoked - if it returns anything else then an exception will be thrown.
The following code examples show how you can create an invocation handler for an interface.
package com.supercoderz.proxy; public interface Test { public int getInt(); public int getIntXXX(); }
An implementation of this interface
package com.supercoderz.proxy; public class TestImpl implements Test { @Override public int getInt() { // TODO Auto-generated method stub return 0; } @Override public int getIntXXX() { // TODO Auto-generated method stub return 0; } }
Now if we want to write a simple invocation handler that delegates the calls to an instance of the TestImpl class then this is what we can do
package com.supercoderz.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class ProxyHandler implements InvocationHandler { Test test; public ProxyHandler(Test test) { this.test = test; } @Override public Object invoke(Object object, Method method, Object[] args) throws Throwable { System.out.println(“Test”); System.out.println(method.getReturnType()); return 1; } }
So how do we use this? Below example show a simple factory method to create this and a sample code on how you can use this transparently in your code. (The factory class code is not shown in full here). public static Test getInstance() { TestImpl test = new TestImpl(); Test proxy = (Test) Proxy.newProxyInstance(Test.class.getClassLoader(), new Class<?>[] { Test.class }, new ProxyHandler(test)); return proxy; } Main code to use this Test proxy = getInstance(); System.out.println(“” + proxy.getInt()); Simple!
From proxies to mock
The transition from a proxy to a mock is pretty simple - in one approach you could create a invocation handler that knows that it is in test mode and returns test values for each method call. This will mean that you return values for all methods, or you throw exceptions for what you do not want to support in mocking. This is a static approach and you cannot add more methods or change results easily.
The other approach which is provided by EasyMock for example, where you specify methods that you expect to call as expect(some method).andReturn(some value) can be easily built using an invocation handler.
What we will do is create an invocation handler that understands that we are creating a mock and can return results that we expect instead of delegating to an instance of the class. As you can see below the code is very generic, not specific to the Test interface and al it does is track methods that we set on it as expectations and the values to return. If a method is not set as expectation then it throws an exception.
package com.supercoderz.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; public class MockProxyHandler implements InvocationHandler { Map<String, Object> expectations = new HashMap<String, Object>(); @Override public Object invoke(Object object, Method method, Object[] args) throws Throwable { if (expectations.containsKey(method.getName())) { return expectations.get(method.getName()); } else { throw new Exception(“No mock assigned to method”); } } public void addExpectation(String method, Object result) { expectations.put(method, result); } }
This invocation handler can be used to create a factory method just like before - in fact you can pass in any class you want and this handler can be used with that. You can also add a helper method to set expectations.
public static Test createMock() { TestImpl test = new TestImpl(); Test proxy = (Test) Proxy.newProxyInstance(Test.class.getClassLoader(), new Class<?>[] { Test.class }, mockProxyHandler); return proxy; } public static void mockResult(String method, Object result) { mockProxyHandler.addExpectation(method, result); }
And then you can use this in your tests without much hassle and check for yourself that the method returns the values that you set when setting the expectation.
Test mock = createMock(); mockResult(“getInt”, new Integer(45)); System.out.println(mock.getInt()); System.out.println(mock.getIntXXX());
Conclusion
Mocking frameworks abstract a lot from us, but the underlying logic is very simple.
Related articles
- Yet More Spock Magic: Mocks (nofluffjuststuff.com)
- tl;dw: Stop mocking, start testing (nedbatchelder.com)
- Mocks And Stubs - Understanding Test Doubles With Mockito (javacodegeeks.com)
- Mockito examples (premaseem.wordpress.com)