Se moquer des méthodes statiques avec Mockito

J’ai écrit une usine pour produire des objects java.sql.Connection :

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

Je voudrais valider les parameters passés à DriverManager.getConnection , mais je ne sais pas comment simuler une méthode statique. J’utilise JUnit 4 et Mockito pour mes tests. Existe-t-il un bon moyen de simuler / vérifier ce cas d’utilisation spécifique?

    Utilisez PowerMockito sur Mockito.

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

    Plus d’information:

    • Pourquoi Mockito ne se moque pas des méthodes statiques?

    La stratégie typique pour éviter les méthodes statiques que vous n’avez aucun moyen d’éviter consiste à créer des objects enveloppés et à utiliser les objects wrapper à la place.

    Les objects wrapper deviennent des façades pour les vraies classes statiques et vous ne les testez pas.

    Un object wrapper pourrait être quelque chose comme

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

    Enfin, votre classe testée peut utiliser cet object singleton, par exemple en ayant un constructeur par défaut pour une utilisation réelle:

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

    Et ici, vous avez une classe qui peut facilement être testée, car vous n’utilisez pas directement une classe avec des méthodes statiques.

    Si vous utilisez CDI et que vous pouvez utiliser l’annotation @Inject, c’est encore plus facile. Il suffit de créer votre bean Wrapper @ApplicationScoped, de l’injecter en tant que collaborateur (vous n’avez même pas besoin de constructeurs désordonnés pour les tests) et de continuer à vous moquer.

    Comme mentionné précédemment, vous ne pouvez pas simuler les méthodes statiques avec mockito.

    Si la modification de votre infrastructure de test n’est pas une option, vous pouvez procéder comme suit:

    Créez une interface pour DriverManager, simulez cette interface, injectez-la via une sorte d’dependency injection et vérifiez sur cette maquette.

    J’ai eu un problème similaire. La réponse acceptée n’a pas fonctionné pour moi jusqu’à ce que j’aie fait le changement: @PrepareForTest(TheClassThatContainsStaticMethod.class) , conformément à la documentation de PowerMock pour mockStatic .

    Et je n’ai pas besoin d’utiliser BDDMockito .

    Ma classe:

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

    Ma classe de test:

     @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.toSsortingng(), SmokeRouteBuilder.smokeMessageId()); } } 

    Pour simuler une méthode statique, vous devriez utiliser un look Powermock sur: https://github.com/powermock/powermock/wiki/MockStatic . Mockito ne fournit pas cette fonctionnalité.

    Vous pouvez lire un article intéressant sur mockito: http://refcardz.dzone.com/refcardz/mockito

    Vous pouvez le faire avec un peu de 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...); } } 

    Ensuite, vous pouvez étendre votre classe MySQLDatabaseConnectionFactory pour renvoyer une connexion MySQLDatabaseConnectionFactory , faire des assertions sur les parameters, etc.

    La classe étendue peut résider dans le scénario de test, si elle se trouve dans le même package (ce que je vous encourage à faire)

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

    Observation: Lorsque vous appelez une méthode statique dans une entité statique, vous devez modifier la classe dans @PrepareForTest.

    Pour par exemple:

     securityAlgo = MessageDigest.getInstance(SECURITY_ALGORITHM); 

    Pour le code ci-dessus, si vous devez simuler la classe MessageDigest, utilisez

     @PrepareForTest(MessageDigest.class) 

    Alors que si vous avez quelque chose comme ci-dessous:

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

    alors, vous devez préparer la classe dans laquelle réside ce code.

     @PrepareForTest(CustomObjectRule.class) 

    Et puis se moquer de la méthode:

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

    J’ai aussi écrit une combinaison de Mockito et AspectJ: https://github.com/iirekm/misc/tree/master/ajmock

    Votre exemple devient:

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

    Mockito ne peut pas capturer de méthodes statiques, mais depuis Mockito 2.14.0, vous pouvez le simuler en créant des instances d’appel de méthodes statiques.

    Exemple (extrait de leurs 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", Ssortingng.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 Ssortingng arg; public Foo(Ssortingng arg) { this.arg = arg; } public static Ssortingng staticMethod(Ssortingng arg) { return ""; } @Override public Ssortingng toSsortingng() { return "foo:" + arg; } } } 

    Leur but n’est pas de supporter directement les moqueries statiques, mais d’améliorer ses APIs publiques pour que d’autres bibliothèques, comme Powermockito , ne soient pas obligées de s’appuyer sur des API internes ou de dupliquer directement du code Mockito. ( source )

    Disclaimer: L’équipe Mockito pense que le chemin de l’enfer est pavé de méthodes statiques. Cependant, le travail de Mockito n’est pas de protéger votre code contre les méthodes statiques. Si vous n’aimez pas que votre équipe fasse de la simulation statique, arrêtez d’utiliser Powermockito dans votre organisation. Mockito doit évoluer comme une boîte à outils avec une vision éclairée sur la manière dont les tests Java doivent être écrits (par exemple, ne pas se moquer de la statique !!!). Cependant, Mockito n’est pas dogmatique. Nous ne voulons pas bloquer les cas d’utilisation non recommandés, tels que le moquage statique. Ce n’est pas notre travail