Monday 25 August 2014

Using PowerMock to Test Singletons

Back to the Job again.

I've been trying to test a small suite of classes based around exception handling. One of the classes is a class factory implemented as a singleton, which has proved impossible to test with the usual techniques, i.e. sub-class and override and Mockito.

A class factory is a class used to create and initialise other classes. A singleton class is designed only to be instantiated once and then every use afterwards uses the same instance over and over again, rather than creating multiple copies of the same class.

Sub-class and override is a technique used to isolate various methods of a class during testing. Take a simple class like this:

public class DemoSuperClass {

    protected List createNameList() {
        List returnList = new ArrayList();

        returnList.add("Steve Stranger");
        returnList.add("James Jones");
        returnList.add("Harry Harnot");
        returnList.add("Bill Blogger");

        return returnList;
    } 

    public void printNameList() {
        List nameList = createNameList();
        for(String name: nameList)
            System.out.printLn(name.split(" ")[0]); //prints the first name
    }

}

Say you want to test printNameList but with an empty list of names, or different names. The best way to do this is in the test harness declare a sub-class of DemoSuperClass and override createNameList:

private class DemoSubClass { //class in the test harness

    @Override
    protected List createNameList() {
        List returnList = new ArrayList();
        return returnList;
    } 
}

Now the sub-class can be tested.

Even if the method being overridden is called by the constructor, it will still work:

public class DemoConstructorClass {

    protected DemoConstructorClass() {
        procedure1();
    }

    protected void procedure1() {
        System.out.println("procedure1");
    }
}

    //in the test harness
    private class TestDemoConstructor extends DemoConstructorClass {

        @Override
        protected void procedure1() {
            System.out.println("Overriden procedure1");
        }
        
    }


Where this won't work is in testing singletons. The singleton I'm trying to test looks something like this:

public class SingletonClass {
    private static SingletonClass instance = new SingletonClass();

    public static SingletonClass getInstance() {
        return instance;
    }
    
    protected SingletonClass() {
        procedure1();
    }

    protected void procedure1() {
        System.out.println("procedure1 called");
    }
}

    //in use, say, in the test harness
    SingletonClass instance = SingletonClass.getInstance();

Notice that the constructor is protected and cannot be accessed from outside the class. The only way to create the class is through getInstance and that returns the private static instance field created when the class is first referenced. Sub-classing and overriding has no effect, because the call to constructor happens as soon as the class gets referenced and the private static instance gets created. Mockito spies don't work either for the same reason. Enter PowerMock.

PowerMock is a set of libraries used to extend existing mock libraries, such as EasyMock and Mockito to cover situations where it's difficult to use them, such as static or final methods and constructors. In particular, the MemberModifier.replace method allows us to override methods (but only static ones) without instantiating the class.

    public static void replacement() {
        System.out.println("replacement");
    }
    
    public void testGetInstance() {
        //procedure1 has to be static to be replaced
        MemberModifier.replace(MemberModifier.method(SingletonClass.class,
                                                     "procedure1"))
                .with(MemberModifier.method(this.getClass(),
                                            "replacement"));
        SingletonClass result = SingletonClass.getInstance();
    }

It's a bit brute-force, and with better design of the singleton class maybe unnecessary, but at least it can now be tested.

No comments:

Post a Comment