Mocking static methods with Mockito

2018-12-31 02:58发布

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

public class MySQLDatabaseConnectionFactory implements DatabaseConnectionFactory {

    @Override public Connection getConnection() {
        try {
            return DriverManager.getConnection(...);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

I'd like to validate the parameters passed to DriverManager.getConnection, but I don't know how to mock a static method. I'm using JUnit 4 and Mockito for my test cases. Is there a good way to mock/verify this specific use-case?

10条回答
素衣白纱
2楼-- · 2018-12-31 03:18

Use PowerMockito on top of Mockito.

Example code:

@RunWith(PowerMockRunner.class)
@PrepareForTest(DriverManager.class)
public class Mocker {

    @Test
    public void testName() throws Exception {

        //given
        PowerMockito.mockStatic(DriverManager.class);
        BDDMockito.given(DriverManager.getConnection(...)).willReturn(...);

        //when
        sut.execute();

        //then
        PowerMockito.verifyStatic();
        DriverManager.getConnection(...);

    }

More information:

查看更多
刘海飞了
3楼-- · 2018-12-31 03:18

I also wrote a combination of Mockito and AspectJ: https://github.com/iirekm/varia/tree/develop/ajmock

Your example becomes:

when(() -> DriverManager.getConnection(...)).thenReturn(...);
查看更多
梦醉为红颜
4楼-- · 2018-12-31 03:28

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);
    }
}
查看更多
春风洒进眼中
5楼-- · 2018-12-31 03:32

The typical strategy for dodging static methods that you have no way of avoiding using, is by creating wrapped objects and using the wrapper objects instead.

The wrapper objects become facades to the real static classes, and you do not test those.

A wrapper object could be something like

public class Slf4jMdcWrapper {
    public static final Slf4jMdcWrapper SINGLETON = new Slf4jMdcWrapper();

    public String myApisToTheSaticMethodsInSlf4jMdcStaticUtilityClass() {
        return MDC.getWhateverIWant();
    }
}

Finally, your class under test can use this singleton object by, for example, having a default constructor for real life use:

public class SomeClassUnderTest {
    final Slf4jMdcWrapper myMockableObject;

    /** constructor used by CDI or whatever real life use case */
    public myClassUnderTestContructor() {
        this.myMockableObject = Slf4jMdcWrapper.SINGLETON;
    }

    /** constructor used in tests*/
    myClassUnderTestContructor(Slf4jMdcWrapper myMock) {
        this.myMockableObject = myMock;
    }
}

And here you have a class that can easily be tested, because you do not directly use a class with static methods.

If you are using CDI and can make use of the @Inject annotation then it is even easier. Just make your Wrapper bean @ApplicationScoped, get that thing injected as a collaborator (you do not even need messy constructors for testing), and go on with the mocking.

查看更多
泪湿衣
6楼-- · 2018-12-31 03:32

Observation : When you call static method within a static entity, you need to change the class in @PrepareForTest.

For e.g. :

securityAlgo = MessageDigest.getInstance(SECURITY_ALGORITHM);

For the above code if you need to mock MessageDigest class, use

@PrepareForTest(MessageDigest.class)

While if you have something like below :

public class CustomObjectRule {

    object = DatatypeConverter.printHexBinary(MessageDigest.getInstance(SECURITY_ALGORITHM)
             .digest(message.getBytes(ENCODING)));

}

then, you'd need to prepare the class this code resides in.

@PrepareForTest(CustomObjectRule.class)

And then mock the method :

PowerMockito.mockStatic(MessageDigest.class);
PowerMockito.when(MessageDigest.getInstance(Mockito.anyString()))
      .thenThrow(new RuntimeException());
查看更多
余生无你
7楼-- · 2018-12-31 03:35

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

查看更多
登录 后发表回答