The Effective Java has the following statement on unit testing singletons
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.
Can anyone explain the why this is so ?
It's very difficult to write unit tests for code that uses singletons because it is generally tightly coupled with the singleton instance, which makes it hard to control the creation of singleton or mock it.
First of all, extract an interface from the Singleton class and then apply Dependency Injection. We need interface for decoupling purpose and unit testing. Accessing singleton class from another class, the instance would then be passed to any objects that might trying to implement it through the property.
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.
If your singleton contains mutable state that cannot be reset between tests, then it will affect unit testing as the tests will need to be run in a specific order and the state accounted for. If your singleton contains state that changes of time, it will prevent you reliably running tests in parallel.
You could use reflection to reset your singleton object to prevent tests from affecting each other.
@Before public void resetSingleton() throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { Field instance = MySingleton.class.getDeclaredField("instance"); instance.setAccessible(true); instance.set(null, null); }
Ref: unit-testing-singletons
The problem isn't testing singletons themselves; the book is saying that if a class you are trying to test depends on a singleton, then you will likely have problems.
Unless, that is, you (1) make the singleton implement an interface, and (2) inject the singleton to your class using that interface.
For example, singletons are typically instantiated directly like this:
public class MyClass { private MySingleton __s = MySingleton.getInstance() ; ... }
MyClass
may now be very difficult to automatedly test. For example, as @Boris Pavlović notes in his answer, if the singleton's behaviour is based on the system time, your tests are now also dependent on the system time, and you may not be able to test cases that, say, depend on the day of the week.
However, if your singleton "implements an interface that serves as its type" then you can still use a singleton implementation of that interface, so long as you pass it in:
public class SomeSingleton implements SomeInterface { ... } public class MyClass { private SomeInterface __s ; public MyClass( SomeInterface s ) { __s = s ; } ... } ... MyClass m = new MyClass( SomeSingleton.getInstance() ) ;
From the perspective of testing MyClass
you now don't care if SomeSingleton
is singleton or not: you can also pass in any other implementation you want, including the singleton implementation, but most likely you'll use a mock of some sort which you control from your tests.
BTW, this is NOT the way to do it:
public class MyClass { private SomeInterface __s = SomeSingleton.getInstance() ; public MyClass() { } ... }
That still works out the same at run-time, but for testing you are now again dependent on SomeSingleton
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With