Singleton and unit testing

前端 未结 12 1520
一生所求
一生所求 2021-01-30 10:24

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 im

相关标签:
12条回答
  • 2021-01-30 10:56

    The problem with singletons (and also with static methods) is that it makes it hard to replace the actual code with a mocked implementation.

    For example consider the following code

    public class TestMe() {
      public String foo(String data) {
        boolean isFeatureFlag = MySingletonConfig.getInstance().getFeatureFlag();
        if (isFeatureFlag)
          // do somethine with data
        else
          // do something else with the data
         return result;
      }
    }
    

    It is not easy to write a unit test for the foo method and verifying the correct behavior is performed. This is because you can't easily change the return value of getFeatureFlag.

    The same problem exists for static methods - it's not easy to replace the actual target class method with a mock behavior.

    Sure, there are workarounds like powermock, or dependency injection to the method, or reflection in tests. But it is much better not to use singletons in the first place

    0 讨论(0)
  • 2021-01-30 10:58

    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

    0 讨论(0)
  • 2021-01-30 11:02

    Singleton objects are created without any control from the outside. In one of the other chapters of the same book Bloch suggests using enums as default Singleton implementation. Let's see an example

    public enum Day {
      MON(2), TUE(3), WED(4), THU(5), FRI(6), SAT(7), SUN(1);
    
      private final int index;
    
      private Day(int index) {
    
        this.index = index;
      }
    
      public boolean isToday() {
    
        return index == new GregorianCalendar().get(Calendar.DAY_OF_WEEK);
      }
    }
    

    Let's say we have a code that should be executed only on weekends:

    public void leisure() {
    
      if (Day.SAT.isToday() || Day.SUN.isToday()) {
    
        haveSomeFun();
        return;
      }
    
      doSomeWork();
    }
    

    Testing leisure method is going to be pretty hard. Its execution is going to be dependent on the day when it is executed. If it executes on a weekday doSomeWork() will be invoked and on weekends haveSomeFun().

    For this case we would need to use some heavy tools like PowerMock to intercept the GregorianCalendar constructor, return a mock which will return an index corresponding to a weekday or weekend in two test cases testing both execution paths of the leisure method.

    0 讨论(0)
  • 2021-01-30 11:05

    Use PowerMock to mock Singleton class (SingletonClassHelper) instance and non-static method (nonStaticMethod) which is called in task.execute().

        import static org.mockito.Mockito.when;
    
        import org.junit.Before;
        import org.junit.Test;
        import org.junit.runner.RunWith;
        import org.mockito.InjectMocks;
        import org.mockito.Mock;
        import org.mockito.Mockito;
        import org.powermock.api.mockito.PowerMockito;
        import org.powermock.core.classloader.annotations.PrepareForTest;
        import org.powermock.modules.junit4.PowerMockRunner;
    
        @PrepareForTest({ SingletonClassHelper.class })
        @RunWith(PowerMockRunner.class)
        public class ClassToTest {
    
            @InjectMocks
            Task task;
    
            private static final String TEST_PAYLOAD = "data";
            private SingletonClassHelper singletonClassHelper;
    
    
            @Before
            public void setUp() {
                PowerMockito.mockStatic(SingletonClassHelper.class);
                singletonClassHelper = Mockito.mock(SingletonClassHelper.class);
                when(SingletonClassHelper.getInstance()).thenReturn(singletonClassHelper);
            }
    
            @Test
            public void test() {
                when(singletonClassHelper.nonStaticMethod(parameterA, parameterB, ...)).thenReturn(TEST_PAYLOAD);
                task.execute();
            }
        }
    
    0 讨论(0)
  • 2021-01-30 11:06

    It's oh so simple.

    In unit-testing, you want to isolate your SUT (the class you're testing). You don't want to test a bunch of classes, because that would defeat the purpose of unit-testing.

    But not all classes do everything on their own, right? Most classes use other classes to do their work, and they kind of mediate between other classes, and add a bit of their own, to get the final result.

    The point is - you don't care about how the classes your SUT depends on work. You care how your SUT works with those classes. That's why you stub or mock the classes your SUT needs. And you can use those mocks because you can pass them in as constructor parameters for your SUT.

    With singletons - the bad thing is that the getInstance() method is globally accessible. That means that you usually call it from within a class, instead of depending on an interface you can later mock. That's why it's impossible to replace it when you want to test your SUT.

    The solution is not to use the sneaky public static MySingleton getInstance() method, but to depend on an interface your class needs to work with. Do that, and you can pass in test doubles whenever you need to.

    0 讨论(0)
  • 2021-01-30 11:08

    I think it actually depends on the implementation of the singleton access pattern.

    For example

    MySingleton.getInstance()
    

    Might be very dificult to test while

    MySingletonFactory mySingletonFactory = ...
    mySingletonFactory.getInstance() //this returns a MySingleton instance or even a subclass
    

    Doesn't provide any information about the fact that its using a singleton. So you can freely replace your factory.

    NOTE: a singleton is defined by being only one instance of that class in an application, however the way it's obtained or stored doesn't have to be through static means.

    0 讨论(0)
提交回复
热议问题