Statische methoden belachelijk maken met Mockito

Ik heb een fabriek geschreven om java.sql.Connection-objecten te produceren:

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

Ik wil graag de parameters valideren die zijn doorgegeven aan DriverManager.getConnection, maar ik weet niet hoe ik een statische methode moet bespotten. Ik gebruik JUnit 4 en Mockito voor mijn testgevallen. Is er een goede manier om deze specifieke use-case te spotten/verifiëren?


Antwoord 1, autoriteit 100%

Gebruik PowerMockitobovenop Mockito.

Voorbeeldcode:

@RunWith(PowerMockRunner.class)
@PrepareForTest(DriverManager.class)
public class Mocker {
    @Test
    public void shouldVerifyParameters() throws Exception {
        //given
        PowerMockito.mockStatic(DriverManager.class);
        BDDMockito.given(DriverManager.getConnection(...)).willReturn(...);
        //when
        sut.execute(); // System Under Test (sut)
        //then
        PowerMockito.verifyStatic();
        DriverManager.getConnection(...);
    }

Meer informatie:


Antwoord 2, autoriteit 21%

De typische strategie om statische methoden te ontwijken die je niet kunt vermijden, is door ingepakte objecten te maken en in plaats daarvan de wrapper-objecten te gebruiken.

De wrapper-objecten worden façades voor de echte statische klassen, en die test je niet.

Een wrapper-object kan zoiets zijn als

public class Slf4jMdcWrapper {
    public static final Slf4jMdcWrapper SINGLETON = new Slf4jMdcWrapper();
    public String myApisToTheSaticMethodsInSlf4jMdcStaticUtilityClass() {
        return MDC.getWhateverIWant();
    }
}

Ten slotte kan de te testen klasse dit singleton-object gebruiken door bijvoorbeeld
een standaardconstructor hebben voor gebruik in het echte leven:

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

En hier heb je een klasse die gemakkelijk kan worden getest, omdat je niet direct een klasse met statische methoden gebruikt.

Als u CDI gebruikt en de @Inject-annotatie kunt gebruiken, is het nog eenvoudiger.
Maak gewoon je Wrapper bean @ApplicationScoped, laat dat ding injecteren als een medewerker (je hebt niet eens rommelige constructors nodig om te testen), en ga door met de spot.


Antwoord 3, autoriteit 14%

Het bespotten van statische methoden in Mockito is mogelijk sinds Mockito 3.4.0.
Voor meer details zie:

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

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

In jouw geval zoiets als dit:

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

OPMERKING: deze functie vereist mockito-inline-afhankelijkheid.

Voor JUnit5:

<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-junit-jupiter</artifactId>
  <version>${mockito.version}</version>
  <scope>test</scope>
</dependency>

Antwoord 4, autoriteit 7%

Ik had een soortgelijk probleem. Het geaccepteerde antwoord werkte niet voor mij, totdat ik de wijziging aanbracht: @PrepareForTest(TheClassThatContainsStaticMethod.class), volgens PowerMock’s documentatie voor mockStatic.

En ik hoef BDDMockitoniet te gebruiken.

Mijn klas:

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

Mijn testles:

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

Antwoord 5, autoriteit 7%

Zoals eerder vermeld, kun je statische methoden niet bespotten met mockito.

Als het wijzigen van uw testkader geen optie is, kunt u het volgende doen:

Maak een interface voor DriverManager, bespot deze interface, injecteer het via een soort afhankelijkheidsinjectie en verifieer op die mock.


Antwoord 6, autoriteit 3%

Voor degenen die JUnit 5 gebruiken, is Powermock geen optie. Je hebt de volgende afhankelijkheden nodig om met succes een statische methode te bespotten met alleen 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-jupiterOndersteunen toevoegen voor JUNIST 5.

en ondersteuning voor spottende statische methoden wordt verstrekt door mockito-inlineafhankelijkheid.

Voorbeeld:

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

Het try-wit-resourceblok wordt gebruikt om de statische spot tijdelijk te laten blijven, dus het wordt alleen bespot in die reikwijdte.

Zorg ervoor dat u de Scoped Mock niet gebruikt, nadat u klaar bent met de beweringen.

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

Mocking-void-methoden:

Wanneer mockStaticwordt opgeroepen op een klasse, worden alle statische void-methoden in die klasse automatisch bespot naar doNothing().


Antwoord 7, Autoriteit 2%

Observatie: wanneer u de statische methode binnen een statische entiteit noemt, moet u de klasse in @preparfortest wijzigen.

voor b.v. :

securityAlgo = MessageDigest.getInstance(SECURITY_ALGORITHM);

Voor de bovenstaande code als u Messagedigest-klasse moet bespotten, gebruikt u

@PrepareForTest(MessageDigest.class)

Terwijl als je iets als hieronder hebt:

public class CustomObjectRule {
    object = DatatypeConverter.printHexBinary(MessageDigest.getInstance(SECURITY_ALGORITHM)
             .digest(message.getBytes(ENCODING)));
}

dan moet je de klas voorbereiden waarin deze code zich bevindt.

@PrepareForTest(CustomObjectRule.class)

En bespot dan de methode:

PowerMockito.mockStatic(MessageDigest.class);
PowerMockito.when(MessageDigest.getInstance(Mockito.anyString()))
      .thenThrow(new RuntimeException());

Antwoord 8, autoriteit 2%

Je kunt het doen met een beetje 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...);
    }
}

Vervolgens kun je je klasse MySQLDatabaseConnectionFactoryuitbreiden om een nepverbinding te retourneren, beweringen over de parameters te doen, enz.

De uitgebreide klasse kan zich in de testcase bevinden, als deze zich in hetzelfde pakket bevindt (wat ik u aanmoedig om te doen)

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

Antwoord 9, autoriteit 2%

Mockito kan geen statische methoden vastleggen, maar sinds Mockito 2.14.0je kunt het simuleren door aanroepinstanties van statische methoden te maken.

Voorbeeld (geëxtraheerd uit ):

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

Hun doel is niet om statische spot rechtstreeks te ondersteunen, maar om de openbare API’s te verbeteren zodat andere bibliotheken, zoals Powermockito, hoeft u niet te vertrouwen op interne API’s of hoeft u niet direct wat Mockito-code te dupliceren. (bron)

Disclaimer: het Mockito-team denkt dat de weg naar de hel geplaveid is met statische methoden. Het is echter niet de taak van Mockito om uw code te beschermen tegen statische methoden. Als je het niet leuk vindt dat je team statisch spotten doet, stop dan met het gebruik van Powermockito in je organisatie. Mockito moet evolueren als een toolkit met een eigenzinnige visie over hoe Java-tests moeten worden geschreven (bijv. Bespot geen statica!!!). Mockito is echter niet dogmatisch. We willen niet-aanbevolen use-cases zoals statische spot niet blokkeren. Het is gewoon niet onze taak.


Antwoord 10, autoriteit 2%

Ik schreef ook een combinatie van Mockito en AspectJ: https://github.com / Iirekm / Varia / Tree / Development / Ajmock

Uw voorbeeld wordt:

when(() -> DriverManager.getConnection(...)).thenReturn(...);

Antwoord 11

Om statische methode te bespotten, moet u een PowerMock-kijk gebruiken naar:
https://github.com/powermock/powermock/wiki/mockstatic .
Mockito geeft niet deze functionaliteit.

je kunt een leuk artikel over Mockito lezen:
http://refcardz.dzone.com/refcardz/mockito


Antwoord 12

Ik heb een oplossing gevonden in Mockito. Deze functie wordt alleen geleverd met een versie van 3.4.0

https://asolntsev.github.io/ EN / 2020 / 07/11 / Mockito-statische methoden /

  • afhankelijkheid

    In uw build.gradle Vervang Mockito-Core: 3.3.3 door Mockito-inline: 3.4.0:

    testImplementation('org.mockito:mockito-inline:3.4.0')
    
  • Wat gaan we bespotten

    class Buddy 
     {
         static String name() 
         {
            return "John";
         }
     }
    
  • Mock de statische methode

       @Test
        void lookMomICanMockStaticMethods() 
        {
             assertThat(Buddy.name()).isEqualTo("John");
            try (MockedStatic<Buddy> theMock = Mockito.mockStatic(Buddy.class)) 
            {
                theMock.when(Buddy::name).thenReturn("Rafael");
                assertThat(Buddy.name()).isEqualTo("Rafael");
            }
            assertThat(Buddy.name()).isEqualTo("John");
        }
    

Ik denk dat dit ons kan helpen.


Antwoord 13

Omdat die methode statisch is, heeft hij al alles wat je nodig hebt om hem te gebruiken, dus het doel van spotten gaat voorbij.
Het bespotten van de statische methoden wordt als een slechte gewoonte beschouwd.

Als je dat probeert, betekent dit dat er iets mis is met de manier waarop je testen wilt uitvoeren.

Natuurlijk kun je PowerMockito of een ander framework gebruiken dat dat kan, maar probeer je aanpak te heroverwegen.

Bijvoorbeeld: probeer de objecten te spotten/leveren, die in plaats daarvan door die statische methode worden gebruikt.


Antwoord 14

Gebruik het JMockit-framework. Het werkte voor mij. U hoeft geen instructies te schrijven voor het bespotten van de DBConenction.getConnection()-methode. Alleen de onderstaande code is voldoende.

@Mock hieronder is mockit.Mock-pakket

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

Antwoord 15

Er is een eenvoudige oplossing door Java FunctionalInterface te gebruiken en die interface vervolgens toe te voegen als afhankelijkheid voor de klasse die u probeert te testen.


Antwoord 16

Voor spottende statische functies kon ik het op die manier doen:

  • Maak een wikkelfunctie in een helperklasse / object. (Het gebruik van een naamvariant kan gunstig zijn voor het scheiden en onderhouden van dingen.)
  • Gebruik deze wrapper in uw codes. (Ja, codes moeten worden gerealiseerd met het testen in gedachten.)
  • Mock the Wrapper-functie.

Wrapper-codefragment (niet echt functioneel, alleen voor illustratie)

class myWrapperClass ...
    def myWrapperFunction (...) {
        return theOriginalFunction (...)
    }

Natuurlijk kan het hebben van meerdere dergelijke functies die in een enkele wrapper-klasse zijn verzameld, gunstig kunnen zijn in termen van codehergebruik.

Other episodes