Mocking static methods with Mockito

后端 未结 15 1093
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:26

    As mention @leokom, since Mockito 3.4.0 you can use the Mockito.mockStatic function to create mocks for static methods. Note that there are patches for this functionality in versions v3.4.2 and v3.4.6, Consider the use of a newer version.

    In your case could be 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")));
        }
      }
    
    0 讨论(0)
  • 2020-11-21 07:28

    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.

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

    Use JMockit framework. It worked for me. You don't have to write statements for mocking DBConenction.getConnection() method. Just the below code is enough.

    @Mock below is mockit.Mock package

    Connection jdbcConnection = Mockito.mock(Connection.class);
    
    MockUp<DBConnection> mockUp = new MockUp<DBConnection>() {
    
                DBConnection singleton = new DBConnection();
    
                @Mock
                public DBConnection getInstance() { 
                    return singleton;
                }
    
                @Mock
                public Connection getConnection() {
                    return jdbcConnection;
                }
             };
    
    0 讨论(0)
  • 2020-11-21 07:30

    I had a similar issue. The accepted answer did not work for me, until I made the change: @PrepareForTest(TheClassThatContainsStaticMethod.class), according to PowerMock's documentation for mockStatic.

    And I don't have to use BDDMockito.

    My class:

    public class SmokeRouteBuilder {
        public static String smokeMessageId() {
            try {
                return InetAddress.getLocalHost().getHostAddress();
            } catch (UnknownHostException e) {
                log.error("Exception occurred while fetching localhost address", e);
                return UUID.randomUUID().toString();
            }
        }
    }
    

    My test class:

    @RunWith(PowerMockRunner.class)
    @PrepareForTest(SmokeRouteBuilder.class)
    public class SmokeRouteBuilderTest {
        @Test
        public void testSmokeMessageId_exception() throws UnknownHostException {
            UUID id = UUID.randomUUID();
    
            mockStatic(InetAddress.class);
            mockStatic(UUID.class);
            when(InetAddress.getLocalHost()).thenThrow(UnknownHostException.class);
            when(UUID.randomUUID()).thenReturn(id);
    
            assertEquals(id.toString(), SmokeRouteBuilder.smokeMessageId());
        }
    }
    
    0 讨论(0)
  • 2020-11-21 07:32

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

    Your example becomes:

    when(() -> DriverManager.getConnection(...)).thenReturn(...);
    
    0 讨论(0)
  • 2020-11-21 07:33

    There is an easy solution by using java FunctionalInterface and then add that interface as dependency for the class you are trying to unit test.

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