Why is a singleton class hard to test?

Effective Java Item 3 (Enforce the singleton property with a private constructor or an enum type) notes that:

Making a class a singleton can make it difficult to test its clients, as it's impossible to substitute a mock implementation for a singleton unless it implements an interface that serves as its type.

For testing purposes, why is it not sufficient to instantiate the one singleton instance and test its APIs? Isn't that what a client would be consuming? The quote seems to imply that testing the singleton would involve a "mock implementation," but why is that necessary?

I've seen various "explanations" that are more or less rephrasings of the quote above. Can someone explain this further, preferably with a code example?


What if your singleton was performing operations on a database or writing data to a file? You would not want that occurring in a unit test. You would want to mock out the object to perform some operations in memory instead so you could verify them without having permanent side effects. Unit tests should be self contained and should not create connections to databases or perform other operations with outside systems that could fail and then cause your unit test to fail for an unrelated reason.

Example with pseudo-java (I'm a C# dev):

public class MySingleton {

    private static final MySingleton instance = new MySingleton();

    private MySingleton() { }

    public int doSomething() {
        //create connection to database, write to a file, etc..
        return something;
    }

    public static MySingleton getInstance() {
        return instance;
    }
}

public class OtherClass {

        public int myMethod() {
            //do some stuff
            int result = MySingleton.getInstance().doSomething();

            //do some other suff
            return something;
        }
}

In order to test myMethod we have to make an actual database call, file operation etc

@Test
public void testMyMethod() {
    OtherClass obj = new OtherClass();

    //if this fails it might be because of some external code called by 
    //MySingleton.doSomething(), not necessarily the logic inside MyMethod()

    Asserts.assertEqual(1, obj.myMethod());
}

If MySingleton was instead something like:

public class MyNonSingleton implements ISomeInterface {

    public MyNonSingleton() {}

    @Override
    public int doSomething() {
        //create connection to database, write to a file, etc..
        return something;
    }

}

you could then inject it as a dependency into MyOtherClass like this:

public class OtherClass {

    private ISomeInterface obj;

    public OtherClass(ISomeInterface obj) {
        this.obj = obj;
    }

    public int myMethod() {
        //do some stuff
        int result = obj.doSomething();

        //do some other stuff
        return something;
    }
}

then you can test like this:

@Test
public void TestMyMethod() {
    OtherClass obj = new OtherClass(new MockNonSingleton());

    //now our mock object can fake the database, filesystem etc. calls to isolate the testing to just the logic in myMethod()

    Asserts.assertEqual(1, obj.myMethod());
}
链接地址: http://www.djcxy.com/p/78798.html

上一篇: Java继承:覆盖或隐藏的方法

下一篇: 为什么单身人士课很难测试?