Mocking static methods with Mockito

后端 未结 15 1143
Happy的楠姐
Happy的楠姐 2020-11-21 06:51

I\'ve written a factory to produce java.sql.Connection objects:

public class MySQLDatabaseConnectionFactory implements DatabaseConnectionFactory         


        
相关标签:
15条回答
  • 2020-11-21 07:34

    To mock static method you should use a Powermock look at: https://github.com/powermock/powermock/wiki/MockStatic. Mockito doesn't provide this functionality.

    You can read nice a article about mockito: http://refcardz.dzone.com/refcardz/mockito

    0 讨论(0)
  • 2020-11-21 07:34

    Mockito cannot capture static methods, but since Mockito 2.14.0 you can simulate it by creating invocation instances of static methods.

    Example (extracted from their tests):

    public class StaticMockingExperimentTest extends TestBase {
    
        Foo mock = Mockito.mock(Foo.class);
        MockHandler handler = Mockito.mockingDetails(mock).getMockHandler();
        Method staticMethod;
        InvocationFactory.RealMethodBehavior realMethod = new InvocationFactory.RealMethodBehavior() {
            @Override
            public Object call() throws Throwable {
                return null;
            }
        };
    
        @Before
        public void before() throws Throwable {
            staticMethod = Foo.class.getDeclaredMethod("staticMethod", String.class);
        }
    
        @Test
        public void verify_static_method() throws Throwable {
            //register staticMethod call on mock
            Invocation invocation = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                    "some arg");
            handler.handle(invocation);
    
            //verify staticMethod on mock
            //Mockito cannot capture static methods so we will simulate this scenario in 3 steps:
            //1. Call standard 'verify' method. Internally, it will add verificationMode to the thread local state.
            //  Effectively, we indicate to Mockito that right now we are about to verify a method call on this mock.
            verify(mock);
            //2. Create the invocation instance using the new public API
            //  Mockito cannot capture static methods but we can create an invocation instance of that static invocation
            Invocation verification = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                    "some arg");
            //3. Make Mockito handle the static method invocation
            //  Mockito will find verification mode in thread local state and will try verify the invocation
            handler.handle(verification);
    
            //verify zero times, method with different argument
            verify(mock, times(0));
            Invocation differentArg = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                    "different arg");
            handler.handle(differentArg);
        }
    
        @Test
        public void stubbing_static_method() throws Throwable {
            //register staticMethod call on mock
            Invocation invocation = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                    "foo");
            handler.handle(invocation);
    
            //register stubbing
            when(null).thenReturn("hey");
    
            //validate stubbed return value
            assertEquals("hey", handler.handle(invocation));
            assertEquals("hey", handler.handle(invocation));
    
            //default null value is returned if invoked with different argument
            Invocation differentArg = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                    "different arg");
            assertEquals(null, handler.handle(differentArg));
        }
    
        static class Foo {
    
            private final String arg;
    
            public Foo(String arg) {
                this.arg = arg;
            }
    
            public static String staticMethod(String arg) {
                return "";
            }
    
            @Override
            public String toString() {
                return "foo:" + arg;
            }
        }
    }
    

    Their goal is not to directly support static mocking, but to improve its public APIs so that other libraries, like Powermockito, don't have to rely on internal APIs or directly have to duplicate some Mockito code. (source)

    Disclaimer: Mockito team thinks that the road to hell is paved with static methods. However, Mockito's job is not to protect your code from static methods. If you don’t like your team doing static mocking, stop using Powermockito in your organization. Mockito needs to evolve as a toolkit with an opinionated vision on how Java tests should be written (e.g. don't mock statics!!!). However, Mockito is not dogmatic. We don't want to block unrecommended use cases like static mocking. It's just not our job.

    0 讨论(0)
  • 2020-11-21 07:35

    For those who use JUnit 5, Powermock is not an option. You'll require the following dependencies to successfully mock a static method with just Mockito.

    testCompile    group: 'org.mockito', name: 'mockito-core',           version: '3.6.0'
    testCompile    group: 'org.mockito', name: 'mockito-junit-jupiter',  version: '3.6.0'
    testCompile    group: 'org.mockito', name: 'mockito-inline',         version: '3.6.0'
    

    mockito-junit-jupiter add supports for JUnit 5.

    And support for mocking static methods is provided by mockito-inline dependency.

    Example:

    @Test
    void returnUtilTest() {
        assertEquals("foo", UtilClass.staticMethod("foo"));
    
        try (MockedStatic<UtilClass> classMock = mockStatic(UtilClass.class)) {
    
            classMock.when(() -> UtilClass.staticMethod("foo")).thenReturn("bar");
    
            assertEquals("bar", UtilClass.staticMethod("foo"));
         }
    
         assertEquals("foo", UtilClass.staticMethod("foo"));
    }
    

    The try-catch block is used to make the static mock remains temporary, so it's mocked only within that scope. But It's not mandatory to use a try-catch block.

    0 讨论(0)
  • 2020-11-21 07:35

    Since that method is static, it already has everything you need to use it, so it defeats the purpose of mocking. Mocking the static methods is considered to be a bad practice.

    If you try to do that, it means there is something wrong with the way you want to perform testing.

    Of course you can use PowerMockito or any other framework capable of doing that, but try to rethink your approach.

    For example: try to mock/provide the objects, which that static method consumes instead.

    0 讨论(0)
  • 2020-11-21 07:37

    You can do it with a little bit of refactoring:

    public class MySQLDatabaseConnectionFactory implements DatabaseConnectionFactory {
    
        @Override public Connection getConnection() {
            try {
                return _getConnection(...some params...);
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    
        //method to forward parameters, enabling mocking, extension, etc
        Connection _getConnection(...some params...) throws SQLException {
            return DriverManager.getConnection(...some params...);
        }
    }
    

    Then you can extend your class MySQLDatabaseConnectionFactory to return a mocked connection, do assertions on the parameters, etc.

    The extended class can reside within the test case, if it's located in the same package (which I encourage you to do)

    public class MockedConnectionFactory extends MySQLDatabaseConnectionFactory {
    
        Connection _getConnection(...some params...) throws SQLException {
            if (some param != something) throw new InvalidParameterException();
    
            //consider mocking some methods with when(yourMock.something()).thenReturn(value)
            return Mockito.mock(Connection.class);
        }
    }
    
    0 讨论(0)
  • 2020-11-21 07:42

    Mocking of static methods in Mockito is possible since Mockito 3.4.0. For more details see:

    https://github.com/mockito/mockito/tree/v3.4.0

    https://github.com/mockito/mockito/issues/1013.

    In your case, something like this:

      @Test
      public void testStaticMockWithVerification() throws SQLException {
        try (MockedStatic<DriverManager> dummy = Mockito.mockStatic(DriverManager.class)) {
          DatabaseConnectionFactory factory = new MySQLDatabaseConnectionFactory();
          dummy.when(() -> DriverManager.getConnection("arg1", "arg2", "arg3"))
            .thenReturn(new Connection() {/*...*/});
    
          factory.getConnection();
    
          dummy.verify(() -> DriverManager.getConnection(eq("arg1"), eq("arg2"), eq("arg3")));
        }
      }
    

    NOTE: this feature requires mockito-inline dependency.

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