random technical thoughts from the Nominet technical team

Stubbing out Spring beans

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...
Posted by dan on Apr 9th, 2008

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><t> proxyClass = (Class</t><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...
        }
    }
}
</t>

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"></bean>
 
    <!-- stubbing out either interfaces or classes is exactly the
         same - just call the factory -->
    <bean id="tokenDao">
</bean>          factory-bean="stubBeanFactory"
          factory-method="createStubBean"&gt;
        <constructor -arg value="uk.nominet.authentication.TokenDao">
    </constructor>
    ...
</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…

One Response

  1. Neale Says:

    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.

Leave a Comment

Please note: Comment moderation is enabled and may delay your comment. There is no need to resubmit your comment.

Recent Posts

Highest Rated

Categories

Archives

Meta: