Mocking a singleton with mockito

后端 未结 7 1051
别跟我提以往
别跟我提以往 2020-12-08 00:50

I need to test some legacy code, which uses a singleton in a a method call. The purpose of the test is to ensure that the clas sunder test makes a call to singletons method.

相关标签:
7条回答
  • 2020-12-08 01:03

    I have a workaround for mocking a Singleton class using reflection. While setting up your tests, you might consider doing the following.

    @Mock 
    private MySingletonClass mockSingleton;
    
    private MySingletonClass originalSingleton;
    
    @Before 
    public void setup() {
        originalSingleton = MySingletonClass.getInstance();
        when(mockSingleton.getSomething()).thenReturn("Something"); // Use the mock to return some mock value for testing
    
        // Now set the instance with your mockSingleton using reflection 
        ReflectionHelpers.setStaticField(MySingletonClass.class, "instance", mockSingleton);
    }
    
    @After
    public void tearDown() {
        // Reset the singleton object when the test is complete using reflection again
        ReflectionHelpers.setStaticField(MySingletonClass.class, "instance", null);
    }
    
    @Test
    public void someTest() {
        // verify something here inside your test function.
    }
    

    The ReflectionHelpers is provided by Robolectric in Android. However, you can always write your own functions which can help you with this. You can check the question here to get an idea.

    0 讨论(0)
  • 2020-12-08 01:04

    As a beginner in Software Development, IMO, dependency injection of a singleton class in a driver/otherservice is a good option. As we can control creation of a single instance of the class and still be able to mock the static methods (as you might have guessed, I have util services in my mind) without using something like PowerMock to mock the static methods (IME which was little painful) I am very much open to listen about it from experienced folks from SOLID or Good OO design principle perspective.

    public class DriverSnapshotHandler {
        private FormatterService formatter;
        public DriverSnapshotHandler() {
            this(FormatterService.getInstance());
        }
        public DriverSnapshotHandler (FormatterService formatterService){
               this.formatter = formatterService;
        }
        public String getImageURL() {
            return FormatterService.getInstance().formatTachoIcon();
        }
    }
    
    and then test using Mockito, something like this.
    
    @Test
    public void testGetUrl(){
      FormatterService formatter = mock(FormatterService.class);
      when(formatter.formatTachoIcon()).thenReturn("TestURL");
      DriverSnapshotHandler handler = new DriverSnapshotHandler(formatter);
      assertEquals(handler.getImageURL(), "TestUrl";
    }
    
    0 讨论(0)
  • 2020-12-08 01:05

    What you are asking is not possible because your legacy code relies on a static method getInstance() and Mockito does not allow to mock static methods, so the following line won't work

    when(FormatterService.getInstance()).thenReturn(formatter);
    

    There are 2 ways around this problem:

    1. Use a different mocking tool, such as PowerMock, that allows to mock static methods.

    2. Refactor your code, so that you don't rely on the static method. The least invasive way I can think of to achieve this is by adding a constructor to DriverSnapshotHandler that injects a FormatterService dependency. This constructor will be only used in tests and you production code will continue to use the real singleton instance.

      public static class DriverSnapshotHandler {
      
          private final FormatterService formatter;
      
          //used in production code
          public DriverSnapshotHandler() {
              this(FormatterService.getInstance());
          }
      
          //used for tests
          DriverSnapshotHandler(FormatterService formatter) {
              this.formatter = formatter;
          }
      
          public String getImageURL() {
              return formatter.formatTachoIcon();
          }
      }
      

    Then, your test should look like this :

    FormatterService formatter = mock(FormatterService.class);
    when(formatter.formatTachoIcon()).thenReturn("MockedURL");
    DriverSnapshotHandler handler = new DriverSnapshotHandler(formatter);
    handler.getImageURL();
    verify(formatter, atLeastOnce()).formatTachoIcon();
    
    0 讨论(0)
  • 2020-12-08 01:13

    I just want to complete the solution from noscreenname. The solution is using PowerMockito. Because PowerMockito can do something like Mockito, So sometimes you can just use PowerMockito .

    The example code is here:

    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.powermock.api.mockito.PowerMockito;
    import org.powermock.core.classloader.annotations.PrepareForTest;
    import org.powermock.modules.junit4.PowerMockRunner;
    
    
    import java.lang.reflect.Field;
    
    import static org.powermock.api.mockito.PowerMockito.mock;
    import static org.powermock.api.mockito.PowerMockito.when;
    
    @RunWith(PowerMockRunner.class)
    @PrepareForTest({Singleton.class})
    public class SingletonTest {
    
        @Test
        public void test_1() {
            // create a mock singleton and change
            Singleton mock = mock(Singleton.class);
            when(mock.dosth()).thenReturn("succeeded");
            System.out.println(mock.dosth());
    
            // insert that singleton into Singleton.getInstance()
            PowerMockito.mockStatic(Singleton.class);
            when(Singleton.getInstance()).thenReturn(mock);
            System.out.println("result:" + Singleton.getInstance().dosth());
        }
    
    }
    

    Singleton class:

    public class Singleton {
    
        private static Singleton INSTANCE;
    
        private Singleton() {
        }
    
        public static Singleton getInstance() {
            if (INSTANCE == null) {
                INSTANCE = new Singleton();
            }
            return INSTANCE;
        }
    
        public String dosth() {
            return "failed";
        }
    
    }
    

    Here is my Gradle:

    /*
    *  version compatibility see: https://github.com/powermock/powermock/wiki/mockito
    *
    * */
    
    def powermock='2.0.2'
    def mockito='2.8.9'
    ...
    dependencies {
        testCompile group: 'junit', name: 'junit', version: '4.12'
    
        /** mock **/
        testCompile group: 'org.mockito', name: 'mockito-core', version: "${mockito}"
    
        testCompile "org.powermock:powermock-core:${powermock}"
        testCompile "org.powermock:powermock-module-junit4:${powermock}"
        testCompile "org.powermock:powermock-api-mockito2:${powermock}"
        /**End of power mock **/
    
    }
    
    0 讨论(0)
  • 2020-12-08 01:15

    If it could helps someone This is my method to test singleton classes You just need to mock all your singleton class and then use doCallRealMethod to really call methods you want to test.

    SingletonClass.java :

    class SingletonClass {
    
        private static SingletonClass sInstance;
    
        private SingletonClass() {
            //do somethings
        }
    
        public static synchronized SingletonClass getInstance() {
            if (sInstance == null) {
                sInstance = new SingletonClass();
            }
    
            return sInstance;
        }
    
        public boolean methodToTest() {
            return true;
        }
    }
    

    SingletonClassTest.java :

    import org.junit.Before;
    import org.junit.Test;
    import org.mockito.Mockito;
    
    import static org.junit.Assert.assertTrue;
    import static org.mockito.Mockito.mock;
    
    public class SingletonClassTest {
    
        private SingletonClass singletonObject;
    
        @Before
        public void setUp() throws Exception {
            singletonObject = mock(SingletonClass.class);
    
            Mockito.doCallRealMethod().when(singletonObject).methodToTest();
        }
    
        @Test
        public void testMethodToTest() {
            assertTrue(singletonObject.methodToTest());
        }
    }
    
    0 讨论(0)
  • 2020-12-08 01:16

    Your getInstance Method is static, thus can't be mocked using mockito. http://cube-drone.com/media/optimized/172.png. You might want to use PowerMockito to do so. Although I would not recommend doing it this way. I would test DriverSnapshotHandler via dependency injection:

    public class DriverSnapshotHandler {
    
        private FormatterService formatterService;
    
        public DriverSnapshotHandler(FormatterService formatterService) {
            this.formatterService = formatterService;
        }
    
        public String getImageURL() {
            return formatterService.formatTachoIcon();
        }
    
    }
    

    The unit test:

    public class TestDriverSnapshotHandler {
    
        private FormatterService formatter;
    
        @Before
        public void setUp() {
    
            formatter = mock(FormatterService.class);
    
            when(formatter.formatTachoIcon()).thenReturn("MockedURL");
    
        }
    
        @Test
        public void testFormatterServiceIsCalled() {
    
            DriverSnapshotHandler handler = new DriverSnapshotHandler(formatter);
            handler.getImageURL();
    
            verify(formatter, times(1)).formatTachoIcon();
    
        }
    
    }
    

    You might want to set the mock to null in a @After method. This is IMHO the cleaner solution.

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