mocking a singleton class

I recently read that making a class singleton makes it impossible to mock the objects of the class, which makes it difficult to test its clients. I could not immediately understand the underlying reason. Can someone please explain what makes it impossible to mock a singleton class? Also, are there any more problems associated with making a class singleton?


Of course, I could write something like don't use singleton, they are evil, use Guice/Spring/whatever but first, this wouldn't answer your question and second, you sometimes have to deal with singleton, when using legacy code for example.

So, let's not discuss the good or bad about singleton (there is another question for this) but let's see how to handle them during testing. First, let's look at a common implementation of the singleton:

public class Singleton {
    private Singleton() { }

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }

    public String getFoo() {
        return "bar";
    }
}

There are two testing problems here:

  • The constructor is private so we we can't extend it (and we can't control the creation of instances in tests but, well, that's the point of singletons).

  • The getInstance is static so it's hard to inject a fake instead of the singleton object in the code using the singleton .

  • For mocking frameworks based on inheritance and polymorphism, both points are obviously big issues. If you have the control of the code, one option is to make your singleton "more testable" by adding a setter allowing to tweak the internal field as described in Learn to Stop Worrying and Love the Singleton (you don't even need a mocking framework in that case). If you don't, modern mocking frameworks based on interception and AOP concepts allow to overcome the previously mentioned problems.

    For example, Mocking Static Method Calls shows how to mock a Singleton using JMockit Expectations.

    Another option would be to use PowerMock, an extension to Mockito or JMock which allows to mock stuff normally not mockable like static, final, private or constructor methods. Also you can access the internals of a class.


    The best way to mock a singleton is not to use them at all, or at least not in the traditional sense. A few practices you might want to look up are:

  • programming to interfaces
  • dependency injection
  • inversion of control
  • So rather than having a single you access like this:

    Singleton.getInstance().doSometing();
    

    ... define your "singleton" as an interface and have something else manage it's lifecycle and inject it where you need it, for instance as a private instance variable:

    @Inject private Singleton mySingleton;
    

    Then when you are unit testing the class/components/etc which depend on the singleton you can easily inject a mock version of it.

    Most dependency injection containers will let you mark up a component as 'singleton', but it's up to the container to manage that.

    Using the above practices makes it much easier to unit test your code and lets you focus on your functional logic instead of wiring logic. It also means your code really starts to become truly Object Oriented, as any use of static methods (including constructors) is debatably procedural. Thus your components start to also become truly reusable.

    Check out Google Guice as a starter for 10:

    http://code.google.com/p/google-guice/

    You could also look at Spring and/or OSGi which can do this kind of thing. There's plenty of IOC / DI stuff out there. :)


    A Singleton, by definition, has exactly one instance. Hence its creation is strictly controlled by the class itself. Typically it is a concrete class, not an interface, and due to its private constructor it is not subclassable. Moreover, it is found actively by its clients (by calling Singleton.getInstance() or an equivalent), so you can't easily use eg Dependency Injection to replace its "real" instance with a mock instance:

    class Singleton {
        private static final myInstance = new Singleton();
        public static Singleton getInstance () { return myInstance; }
        private Singleton() { ... }
        // public methods
    }
    
    class Client {
        public doSomething() {
            Singleton singleton = Singleton.getInstance();
            // use the singleton
        }
    }
    

    For mocks, you would ideally need an interface which can be freely subclassed, and whose concrete implementation is provided to its client(s) by dependency injection.

    You can relax the Singleton implementation to make it testable by

  • providing an interface which can be implemented by a mock subclass as well as the "real" one
  • adding a setInstance method to allow replacing the instance in unit tests
  • Example:

    interface Singleton {
        private static final myInstance;
        public static Singleton getInstance() { return myInstance; }
        public static void setInstance(Singleton newInstance) { myInstance = newInstance; }
        // public method declarations
    }
    
    // Used in production
    class RealSingleton implements Singleton {
        // public methods
    }
    
    // Used in unit tests
    class FakeSingleton implements Singleton {
        // public methods
    }
    
    class ClientTest {
        private Singleton testSingleton = new FakeSingleton();
        @Test
        public void test() {
            Singleton.setSingleton(testSingleton);
            client.doSomething();
            // ...
        }
    }
    

    As you see, you can only make your Singleton-using code unit testable by compromising the "cleanness" of the Singleton. In the end, it is best not to use it at all if you can avoid it.

    Update: And here is the obligatory reference to Working Effectively With Legacy Code by Michael Feathers.

    链接地址: http://www.djcxy.com/p/13820.html

    上一篇: 什么是隐藏在Java中的方法? 即使是JavaDoc的解释也是令人困惑的

    下一篇: 嘲笑一个单身人士课程