example java unit-testing mocking mockito

java - example - Burlarse de los métodos estáticos con Mockito.



powermock example (10)

He escrito una fábrica para producir objetos java.sql.Connection :

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

Me gustaría validar los parámetros pasados ​​a DriverManager.getConnection , pero no sé cómo simular un método estático. Estoy usando JUnit 4 y Mockito para mis casos de prueba. ¿Hay una buena manera de simular / verificar este caso de uso específico?


Como se mencionó anteriormente, no puedes simular métodos estáticos con mockito.

Si cambiar su marco de prueba no es una opción, puede hacer lo siguiente:

Cree una interfaz para DriverManager, simule esta interfaz, inyecte a través de algún tipo de inyección de dependencia y verifique en esa simulación.


La estrategia típica para esquivar métodos estáticos que no tiene forma de evitar usarlos es crear objetos envueltos y, en su lugar, utilizar objetos envoltorios.

Los objetos de envoltura se convierten en fachadas de las clases estáticas reales, y no las prueba.

Un objeto envoltorio podría ser algo así como

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

Finalmente, su clase bajo prueba puede usar este objeto singleton, por ejemplo, teniendo un constructor predeterminado para uso en la vida real:

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

Y aquí tiene una clase que puede probarse fácilmente, porque no usa directamente una clase con métodos estáticos.

Si está utilizando CDI y puede hacer uso de la anotación @Inject, entonces es aún más fácil. Simplemente haga su Wrapper bean @ApplicationScoped, inyecte esa cosa como un colaborador (ni siquiera necesita constructores desordenados para la prueba), y continúe con la burla.


Mockito no puede capturar métodos estáticos, pero desde Mockito 2.14.0 puede simularlo creando instancias de invocación de métodos estáticos.

Ejemplo (extraído de sus pruebas ):

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

Su objetivo no es apoyar directamente el simulacro estático, sino mejorar sus API públicas para que otras bibliotecas, como PowerMockito , no tengan que depender de las API internas o directamente tengan que duplicar algún código de Mockito. ( source )

Descargo de responsabilidad: el equipo de Mockito cree que el camino al infierno está pavimentado con métodos estáticos. Sin embargo, el trabajo de Mockito no es proteger su código de métodos estáticos. Si no te gusta que tu equipo haga burla estática, deja de usar Powermockito en tu organización. Mockito necesita evolucionar como un conjunto de herramientas con una visión de opinión sobre cómo se deben escribir las pruebas de Java (por ejemplo, ¡¡no te burles de las estadísticas !!) Sin embargo, Mockito no es dogmático. No queremos bloquear los casos de uso no recomendados como la burla estática. Simplemente no es nuestro trabajo.


Observación: cuando llama a un método estático dentro de una entidad estática, necesita cambiar la clase en @PrepareForTest.

Por ejemplo:

securityAlgo = MessageDigest.getInstance(SECURITY_ALGORITHM);

Para el código anterior, si necesita simular una clase MessageDigest, use

@PrepareForTest(MessageDigest.class)

Mientras que si tienes algo como el siguiente:

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

entonces, necesitarías preparar la clase en la que reside este código.

@PrepareForTest(CustomObjectRule.class)

Y luego burlarse del método:

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



Puedes hacerlo con un poco de refactorización:

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

Luego, puede ampliar su clase MySQLDatabaseConnectionFactory para devolver una conexión MySQLDatabaseConnectionFactory , hacer afirmaciones sobre los parámetros, etc.

La clase extendida puede residir dentro del caso de prueba, si se encuentra en el mismo paquete (lo cual le recomiendo que haga)

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



Tuve un problema similar La respuesta aceptada no me funcionó hasta que hice el cambio: @PrepareForTest(TheClassThatContainsStaticMethod.class) , según la documentación de PowerMock para mockStatic .

Y no tengo que usar BDDMockito .

Mi clase:

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

Mi clase de prueba:

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


Usa PowerMockito encima de Mockito.

Código de ejemplo:

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

Más información:


Usa el framework JMockit . Funcionó para mí. No tiene que escribir declaraciones para simular el método DBConenction.getConnection (). Sólo el código de abajo es suficiente.

@Mock a continuación es el paquete mockit.Mock

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