504 Stimmen

Das Mocken von statischen Methoden mit Mockito

Ich habe eine Fabrik geschrieben, um java.sql.Connection Objekte zu erzeugen:

public class MySQLDatabaseConnectionFactory implements DatabaseConnectionFactory {

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

Ich möchte die Parameter überprüfen, die an DriverManager.getConnection übergeben werden, aber ich weiß nicht, wie man eine statische Methode mockt. Ich benutze JUnit 4 und Mockito für meine Testfälle. Gibt es eine gute Möglichkeit, diesen speziellen Anwendungsfall zu mocken/überprüfen?

414voto

MariuszS Punkte 29175

Verwenden Sie PowerMockito über Mockito.

Beispielcode:

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

    @Test
    public void shouldVerifyParameters() throws Exception {

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

        //wenn
        sut.execute(); // System Under Test (sut)

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

    }

Weitere Informationen:

132voto

leokom Punkte 1553

Das Mocken von statischen Methoden in Mockito ist seit Mockito 3.4.0 möglich. Für weitere Details siehe:

https://github.com/mockito/mockito/releases/tag/v3.4.0

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

https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#static_mocks

assertEquals("foo", Foo.method());
try (MockedStatic mocked = mockStatic(Foo.class)) {
 mocked.when(Foo::method).thenReturn("bar");
 assertEquals("bar", Foo.method());
 mocked.verify(Foo::method);
}
assertEquals("foo", Foo.method());

In deinem Fall, etwas in dieser Art:

  @Test
  public void testStaticMockWithVerification() throws SQLException {
    try (MockedStatic 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")));
    }
  }

(Vor Mockito 5.0.0 erforderte das Mocken von statischen Methoden eine zusätzliche Abhängigkeit von mockito-inline)

Für JUnit5 füge auch dies hinzu:

  org.mockito
  mockito-junit-jupiter
  ${mockito.version}
  test

88voto

99Sono Punkte 3408

Die typische Strategie, um statische Methoden zu umgehen, die Sie nicht umgehen können, besteht darin, Wrapper-Objekte zu erstellen und stattdessen die Wrapper-Objekte zu verwenden.

Die Wrapper-Objekte werden zu Fassaden für die realen statischen Klassen, und Sie testen diese nicht.

Ein Wrapper-Objekt könnte etwas wie

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

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

Schließlich kann Ihre Testklasse dieses Singleton-Objekt verwenden, indem Sie beispielsweise einen Standardkonstruktor für den tatsächlichen Gebrauch haben:

public class SomeClassUnderTest {
    final Slf4jMdcWrapper myMockableObject;

    /** Konstruktor, der von CDI oder einem anderen realen Anwendungsfall verwendet wird */
    public myClassUnderTestContructor() {
        this.myMockableObject = Slf4jMdcWrapper.SINGLETON;
    }

    /** Konstruktor, der in Tests verwendet wird */
    myClassUnderTestContructor(Slf4jMdcWrapper myMock) {
        this.myMockableObject = myMock;
    }
}

Und hier haben Sie eine Klasse, die leicht getestet werden kann, weil Sie keine Klasse mit statischen Methoden direkt verwenden.

Wenn Sie CDI verwenden und die @Inject-Annotation nutzen können, ist es noch einfacher. Machen Sie einfach Ihr Wrapper-Bean @ApplicationScoped, lassen Sie dieses Ding als Kollaborator injizieren (Sie benötigen nicht einmal komplizierte Konstruktoren für Tests) und fahren Sie mit dem Mocking fort.

31voto

6324 Punkte 4318

Ich hatte ein ähnliches Problem. Die akzeptierte Antwort hat bei mir nicht funktioniert, bis ich die Änderung vorgenommen habe: @PrepareForTest(TheClassThatContainsStaticMethod.class), gemäß PowerMock's Dokumentation für mockStatic.

Und ich muss nicht BDDMockito verwenden.

Meine Klasse:

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();
        }
    }
}

Meine Testklasse:

@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());
    }
}

27voto

Gayan Weerakutti Punkte 8759

Für diejenigen, die JUnit 5 verwenden, ist Powermock keine Option. Sie benötigen die folgenden Abhängigkeiten, um erfolgreich eine statische Methode nur mit Mockito zu mocken.

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 fügt Unterstützung für JUnit 5 hinzu.

Und die Unterstützung für das Mocken von statischen Methoden wird durch die Abhängigkeit von mockito-inline bereitgestellt.

Beispiel:

@Test
void returnUtilTest() {
    assertEquals("foo", UtilClass.staticMethod("foo"));

    try (MockedStatic classMock = mockStatic(UtilClass.class)) {

        classMock.when(() -> UtilClass.staticMethod("foo")).thenReturn("bar");

        assertEquals("bar", UtilClass.staticMethod("foo"));
     }

     assertEquals("foo", UtilClass.staticMethod("foo"));
}

Der try-with-resource-Block wird verwendet, um sicherzustellen, dass das statische Mockobjekt nur vorübergehend bleibt und nur innerhalb dieses Bereichs gemockt wird.

Wenn kein try-Block verwendet wird, stellen Sie sicher, dass Sie das Mockobjekt schließen, sobald Sie mit den Überprüfungen fertig sind.

MockedStatic classMock = mockStatic(UtilClass.class)
classMock.when(() -> UtilClass.staticMethod("foo")).thenReturn("bar");
assertEquals("bar", UtilClass.staticMethod("foo"));
classMock.close();

Mocken von void Methoden:

Wenn mockStatic auf eine Klasse angewendet wird, werden automatisch alle statischen void Methoden in dieser Klasse gemockt, um doNothing().

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X