Stubbing out Spring beans
For any number of reasons, you may find yourself wanting to create only part of a Spring context without satisfying all of its @Required dependencies (and their dependencies, and their dependencies’ dependencies…) with real objects:
- Reducing the number of Spring beans your integration tests need can drastically speed up your unit tests, since Spring container startup can take a while and consume a lot of heap space.
- In my case I wanted to write an integration test that only needed a thin vertical slice of beans to get Hibernate working, without the (rather large number of) other beans in the rest of the context definition.
In conventional unit testing, this is easy: you use mocks or stubs at the boundaries of your test objects. Why not extend this to Spring beans?
By rejigging some import statements, I was able to use Spring’s instance-factory bean declarations and dynamic mocking to do just that. The process has two parts: creating the factory class, and declaring the stub beans…
Creating the factory
package uk.nominet.testing; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * Stub spring bean factory class. Call createStubBean("classname") to stub out a bean. */ public class StubBeanFactory { /** * Factory method to create stub beans. * @param className to create a stub bean for. * @return stub bean. * @throws ClassNotFoundException if className doesn't match a real class. */ @SuppressWarnings("Unchecked") public <T> T createStubBean(String className) throws ClassNotFoundException { // get the class to proxy Class<T> proxyClass = (Class<T>)Class.forName(className); // the specifics of proxy creation depend upon whether we need to proxy a class // or an interface - which is it? if (proxyClass.isInterface()) { // proxying interfaces is easy with the standard java proxying classes return (T)Proxy.newProxyInstance(proxyClass.getClassLoader(), new Class[]{proxyClass}, new StubMethodHandler()); } else { // proxying classes requires some cglib magic... Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(proxyClass); enhancer.setCallback(new StubMethodHandler()); return (T)enhancer.create(); } } /** * Helper class to handle method invocations on stub beans. */ private static class StubMethodHandler implements InvocationHandler, MethodInterceptor { /** * @see InvocationHandler#invoke(Object, Method, Object[]) */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // you could put some logging here... } /** * @see MethodInterceptor#intercept(Object, Method, Object[], MethodProxy) */ public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { // you could put some logging here... } } }
Creating stub beans
Stubbing out a spring bean is simply a case of creating and using the factory:
<beans> ... <!-- this is our stub bean factory --> <bean id="stubBeanFactory" class="uk.nominet.testing.StubBeanFactory"/> <!-- stubbing out either interfaces or classes is exactly the same - just call the factory --> <bean id="tokenDao" factory-bean="stubBeanFactory" factory-method="createStubBean"> <constructor-arg value="uk.nominet.authentication.TokenDao"/> </bean> ... </beans>
What’s the point?
By stubbing out the unneeded beans:
- the number of beans in the testing context was reduced from nearly 400 to less than 40;
- the running time for the integration test was reduced from 40 seconds to 10 seconds.
The second point here is key - faster tests leads to tests that get run at all…


April 25th, 2008 at 11:36 am
Nice one! Your point about reducing the test time being key is far too true. Prior to us setting up a continuous integration system, we found we got complacent around running some of our unit tests, because they took too long to do at the UI.
While we’ve solved that problem (we’re using Luntbuild), we still want to encourage developers to run the tests on their desktop, as it gives us a much broader coverage of JVMs, OSes and platforms (e.g. single core vs multiple cores).
Thanks for taking the time to blog.