Home > Java > Mockito’ spy() method and Spring

Mockito’ spy() method and Spring

Mockito is a mocking framework (see Two different mocking approaches) that is an offshoot of EasyMock. Whatever the mocking framework one uses, a common feature is the ability to mock interfaces, through the JDK Proxy class. This is well and nice, but one has to explicitly mock every method that one wants to use in the course of the test.

What if I want to mock an already existing implementation, with some methods providing behaviours that suit me ? Today, I ran across this case: I had a legacy helper class I wanted to reuse. This class used commons-http-client to ease the process of calling an URL. It had property accessors, like any good old POJO, that I really needed even in the test scope and a method that made the real call, using previously set properties (such as URL). It implemented no interface, though. Here’s what it looked like:

public class LegacyHelper {

    // Various attributes
    ...

    // Various accessors to get/set these properties
    ...

    // One big method that uses external resources (very bad for Unit Testing)
    public int callUrl() {

        ...
    }
}

Although Mockito lacks exhaustive documentation (a trait shared by many Google Code projects, much to my dismay), I happened to run across the Mockito.spy() method. This magic method creates a proxy (hence the name spy) on a real object. It delegates its method calls to the proxied object unless these methods are stubbed. It means I could rely on the getters/setters doing their work while neutralizing the legacy method that broke isolation testing.

public class MyTest {

    // This I don't want to test but my class uses it
    private LegacyHelper helper;

    @BeforeMethod
    public void setUp() {

        helper = Mockito.spy(new LegacyHelper());

        Mockito.when(helper.callUrl()).thenReturn(0);
    }

    @Test
    public void testCall() {

        // Now I can use helper without it really calling anything
        helper.callUrl();

        // Do real testing here
       ...
    }

}

This is only the first step. What if I need to provide spied objects throughout the entire application? Spring certainly helps here with the FactoryBean interface. When Spring creates a new instance, either it calls the new operator or the getObject() method if the referenced class is of type FactoryBean. Our spy factory looks like this:

public class SpyFactoryBean {

    // Real or spied object
    private Object real;

    public void setReal(Object object) {

        real = object;
    }

    public boolean isSingleton() {

        return false;
    }

    public Class getObjectType() {

        return real.getClass();
    }

    public Object getObject() {

        return Mock.spy(real);
    }
}

To use it in a Spring context file:

<?xml version="1.0" encoding="ISO-8859-1"?>
<beans>

    <bean id="legacyHelper" class="LegacyHelper" />

    <bean id="mockHelper" class="SpyFactoryBean" dependency-check="objects">
        <property name="real" ref="legacyHelper" />
    </bean>
</beans>

Now you’ve got a factory of spies that you can reuse across your project’s tests or even better, ship for use among all your enterprise’s projects. The only thing to do is not to forget to stub the methods that may have side-effects.

email
Send to Kindle
  1. kubek2k
    March 1st, 2011 at 20:04 | #1
  2. July 20th, 2012 at 14:51 | #2

    Great post! :)

    Recently, I posted a similar entry in my blog http://pablocantero.com/blog/2012/07/19/refactoring-legacy-code—reducing-coupling/

    I don’t know, but I don’t feel confortable with the idea of using test subjects from Mockito/CGLIB. Even they acting as a proxy, only intercepting methods that I specified to “`doReturn …. “`.

    What do you think about the Anonymous class approach?

    “`
    subject = new LegacyHelper(){
        public int callUrl() {
            return 0;
        }
    }
    “`

    It is a little boilerplate, but there isn’t a magical/obscure thing behind it.

    Cheers

  3. July 20th, 2012 at 17:02 | #3

    Whatever works. And yet, where’s the need for Mockito when you could always use anonymous classes?

  4. July 20th, 2012 at 18:34 | #4

    @Nicolas Frankel

    You can use Mockito for all classes that aren’t the subject in your unit tests.

  1. No trackbacks yet.