Why is a singleton class hard to test?

Effective Java . Clause 3 (Enforcing the use of the singleton property with a private constructor or enumeration type) notes that:

Creating a singleton class can make it difficult to validate its clients, since it is not possible to replace the mock implementation for singleton unless it implements an interface that serves as its type.

For testing purposes, why is this not enough to instantiate one singleton instance and test its API? Isn't that what the customer will consume? The quote seems to imply that singleton testing will include a “mock implementation”, but why is this necessary?

I have seen various “explanations” that are more or less paraphrased quotes above. Can someone explain this further, preferably with sample code?

+8
source share
2 answers

What if your singleton performed operations on a database or wrote data to a file? You do not want this to happen in unit test. You would like to mock an object to perform some operations in memory instead, so that you can test them without constant side effects. Unit tests must be self-contained and must not create database connections or perform other operations with external systems that may fail, and then cause your unit test to fail for another reason.

An example with pseudo-java (I am a C # developer):

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; } } 

To test myMethod , we need to make the actual database call, work with the file, 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 instead of MySingleton there was 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 can embed it as a dependency in MyOtherClass as follows:

 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 as follows:

 @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()); } 
+9
source

Personally, I believe that this statement is completely incorrect , since it assumes that the singleton cannot be replaced (modeled) for unit tests. On the contrary. For example, in the Spring dependency injection, the singleton model is the default model for DI components. Singletones and dependency injection are not mutually exclusive, which somehow tries to make the statement above.

I agree that anything that cannot be modeled makes testing the application difficult, but there is no reason to believe that singletones are less fake than any other objects in your application .

The problem may be that the singleton is one global instance, and when it can be in too many different states, unit tests can show unpredictable results due to a change in the state of the singleton. But there are simple solutions for this - make fun of your singleton and make it so that you have fewer conditions. Or write your tests in such a way that the singleton is recreated (or reinitialized) before each unit test that depends on it. Or, the best solution, test your application for all possible states of a singleton. Ultimately, if reality requires several states, such as, for example, connecting to a database (disconnected / connected / connected / error / ...), then you will have to deal with this regardless of whether you use singletones or not .

0
source

Source: https://habr.com/ru/post/973722/


All Articles