tutorial mock examples español java junit mockito

java - examples - Cómo burlarse de una clase final con mockito



mockito tutorial español (18)

Ahorro de tiempo para las personas que enfrentan el mismo problema (Mockito + Final Class) en Android + Kotlin. Como en Kotlin, las clases son finales por defecto. Encontré una solución en una de las muestras de Google Android con el componente Arquitectura. Solución elegida desde aquí: https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample

Crea las siguientes anotaciones:

/** * This annotation allows us to open some classes for mocking purposes while they are final in * release builds. */ @Target(AnnotationTarget.ANNOTATION_CLASS) annotation class OpenClass /** * Annotate a class with [OpenForTesting] if you want it to be extendable in debug builds. */ @OpenClass @Target(AnnotationTarget.CLASS) annotation class OpenForTesting

Modifique su archivo gradle. Tome ejemplo de aquí: https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/build.gradle

apply plugin: ''kotlin-allopen'' allOpen { // allows mocking for classes w/o directly opening them for release builds annotation ''com.android.example.github.testing.OpenClass'' }

Ahora puede anotar cualquier clase para que esté abierta para la prueba:

@OpenForTesting class RepoRepository

Tengo una clase final, algo como esto:

public final class RainOnTrees{ public void startRain(){ // some code here } }

Estoy usando esta clase en alguna otra clase como esta:

public class Seasons{ RainOnTrees rain = new RainOnTrees(); public void findSeasonAndRain(){ rain.startRain(); } }

y en mi clase de prueba JUnit para Seasons.java quiero burlarme de la clase RainOnTrees . ¿Cómo puedo hacer esto con Mockito?


Como otros han declarado, esto no funcionará de la caja con Mockito. Sugeriría usar la reflexión para establecer los campos específicos en el objeto que está siendo utilizado por el código bajo prueba. Si te encuentras haciendo esto mucho, puedes envolver esta funcionalidad en una biblioteca.

Como comentario aparte, si eres el que marca las clases finales, deja de hacer eso. Me encontré con esta pregunta porque estoy trabajando con una API donde todo se marcó como definitivo para evitar mi legítima necesidad de extensión (burla), y me gustaría que el desarrollador no supusiera que nunca necesitaría extender la clase.


En realidad, hay una forma, que uso para espiar. Trabajaría para usted solo si se cumplen dos condiciones previas:

  1. Usas algún tipo de DI para inyectar una instancia de clase final
  2. La clase final implementa una interfaz

Por favor, recuerde el Item 16 de Effective Java . Puede crear un contenedor (no final) y reenviar todas las llamadas a la instancia de la clase final:

public final class RainOnTrees implement IRainOnTrees { @Override public void startRain() { // some code here } } public class RainOnTreesWrapper implement IRainOnTrees { private IRainOnTrees delegate; public RainOnTreesWrapper(IRainOnTrees delegate) {this.delegate = delegate;} @Override public void startRain() { delegate.startRain(); } }

Ahora no solo puedes burlarte de tu clase final sino también espiarla:

public class Seasons{ RainOnTrees rain; public Seasons(IRainOnTrees rain) { this.rain = rain; }; public void findSeasonAndRain(){ rain.startRain(); } } IRainOnTrees rain = spy(new RainOnTreesWrapper(new RainOnTrees()) // or mock(IRainOnTrees.class) doNothing().when(rain).startRain(); new Seasons(rain).findSeasonAndRain();


Esto se puede hacer si está usando Mockito2, con la nueva función de incubación que admite la burla de las clases y métodos finales.

Puntos clave a tener en cuenta:
1. Cree un archivo simple con el nombre "org.mockito.plugins.MockMaker" y colóquelo en una carpeta llamada "mockito-extensions". Esta carpeta debe estar disponible en classpath.
2. El contenido del archivo creado anteriormente debe ser una sola línea como se indica a continuación:
mock-maker-inline

Los dos pasos anteriores son necesarios para activar el mecanismo de extensión de mockito y utilizar esta función de aceptación.

Las clases de muestra son las siguientes:

FinalClass.java

public final class FinalClass { public final String hello(){ System.out.println("Final class says Hello!!!"); return "0"; }

}

Foo.java

public class Foo { public String executeFinal(FinalClass finalClass){ return finalClass.hello(); }

}

FooTest.java

public class FooTest { @Test public void testFinalClass(){ // Instantiate the class under test. Foo foo = new Foo(); // Instantiate the external dependency FinalClass realFinalClass = new FinalClass(); // Create mock object for the final class. FinalClass mockedFinalClass = mock(FinalClass.class); // Provide stub for mocked object. when(mockedFinalClass.hello()).thenReturn("1"); // assert assertEquals("0", foo.executeFinal(realFinalClass)); assertEquals("1", foo.executeFinal(mockedFinalClass)); }

}

Espero eso ayude.

Artículo completo presente aquí mocking-the-unmockable .


Las soluciones proporcionadas por RC y Luigi R. Viggiano juntos son posiblemente la mejor idea.

Aunque Mockito no puede , por diseño, burlarse de las clases finales, el enfoque de delegación es posible . Esto tiene sus ventajas:

  1. No está obligado a cambiar su clase a no final si eso es lo que su API pretende en primer lugar (las clases finales tienen sus benefits ).
  2. Estás probando la posibilidad de una decoration alrededor de tu API.

En su caso de prueba, reenvía deliberadamente las llamadas al sistema bajo prueba. Por lo tanto, por diseño, su decoración no hace nada.

Por lo tanto, la prueba también puede demostrar que el usuario solo puede decorar la API en lugar de ampliarla.

En una nota más subjetiva: prefiero mantener los marcos al mínimo, por lo que JUnit y Mockito suelen ser suficientes para mí. De hecho, restringir de esta manera a veces me obliga a refactorizar para bien también.


No intenté el final, pero para el privado, usando la reflexión ¡elimine el modificador trabajado! Debería funcionar para final también.


No puedes burlarte de una clase final con Mockito, ya que no puedes hacerlo por ti mismo.

Lo que hago es crear una clase no final para envolver la clase final y usarla como delegado. Un ejemplo de esto es la clase TwitterFactory , y esta es mi clase simulable:

public class TwitterFactory { private final twitter4j.TwitterFactory factory; public TwitterFactory() { factory = new twitter4j.TwitterFactory(); } public Twitter getInstance(User user) { return factory.getInstance(accessToken(user)); } private AccessToken accessToken(User user) { return new AccessToken(user.getAccessToken(), user.getAccessTokenSecret()); } public Twitter getInstance() { return factory.getInstance(); } }

La desventaja es que hay un montón de código repetitivo; la ventaja es que puede agregar algunos métodos que pueden relacionarse con su negocio de aplicaciones (como el getInstance que está llevando a un usuario en lugar de un accessToken, en el caso anterior).

En tu caso, crearía una clase RainOnTrees no final que delegaría en la clase final. O, si puede hacerlo no final, sería mejor.


Otra solución, que puede aplicarse en algunos casos, es crear una interfaz que se implementa por esa clase final, cambiar el código para usar la interfaz en lugar de la clase concreta y luego simular la interfaz. Esto le permite separar el contrato (interfaz) de la implementación (clase final). Por supuesto, si lo que quieres es en realidad vincular a la clase final, esto no se aplicará.


Por favor, mira JMockit . Tiene una extensa documentación con muchos ejemplos. Aquí tienes una solución de ejemplo de tu problema (para simplificar, he agregado un constructor a Seasons para inyectar la instancia de RainOnTrees burlada):

package jmockitexample; import mockit.Mocked; import mockit.Verifications; import mockit.integration.junit4.JMockit; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(JMockit.class) public class SeasonsTest { @Test public void shouldStartRain(@Mocked final RainOnTrees rain) { Seasons seasons = new Seasons(rain); seasons.findSeasonAndRain(); new Verifications() {{ rain.startRain(); }}; } public final class RainOnTrees { public void startRain() { // some code here } } public class Seasons { private final RainOnTrees rain; public Seasons(RainOnTrees rain) { this.rain = rain; } public void findSeasonAndRain() { rain.startRain(); } } }


Prueba esto:

Mockito.mock(SomeMockableType.class,AdditionalAnswers.delegatesTo(someInstanceThatIsNotMockableOrSpyable));

Funcionó para mí "SomeMockableType.class" es la clase principal de lo que quiere burlarse o espiar, y someInstanceThatIsNotMockableOrSpyable es la clase real que desea simular o espiar.

Para más detalles echa un vistazo here


Sí, el mismo problema aquí, no podemos burlarnos de una clase final con Mockito. Para ser preciso, Mockito no puede burlarse / espiar a continuación:

  • clases finales
  • clases anónimas
  • tipos primitivos

Pero el uso de una clase contenedora me parece un gran precio a pagar, así que, en su lugar, obtenga PowerMockito.


Solo para seguir. Por favor agregue esta línea a su archivo gradle:

testCompile group: ''org.mockito'', name: ''mockito-inline'', version: ''2.8.9''

He probado varias versiones de mockito-core y mockito-all. Ninguno de ellos trabaja.


Supongo que lo hizo final porque quiere evitar que otras clases RainOnTrees . Como sugiere Java con eficacia (elemento 15), hay otra manera de mantener una clase cerca para la extensión sin que sea final :

  1. Eliminar la palabra clave final ;

  2. Haga su constructor private . Ninguna clase podrá ampliarlo porque no podrá llamar al super ;

  3. Crea un método de fábrica estático para crear instancias de tu clase.

    // No more final keyword here. public class RainOnTrees { public static RainOnTrees newInstance() { return new RainOnTrees(); } private RainOnTrees() { // Private constructor. } public void startRain() { // some code here } }

Al usar esta estrategia, podrás usar Mockito y mantener tu clase cerrada para la extensión con un pequeño código repetitivo.



Yo tuve el mismo problema. Como la clase que estaba tratando de burlar era una clase simple, simplemente creé una instancia y la devolví.


agregue esto en su archivo gradle:

testCompile ''org.mockito:mockito-inline:2.13.0''

esta es una configuración para hacer que el mockito funcione con las clases finales


¡Mockito 2 ahora admite clases y métodos finales !

Pero por ahora eso es una función de "incubación". Requiere algunos pasos para activarlo que se describen en Novedades en Mockito 2 :

La burla de las clases y los métodos finales es una función de inclusión incubativa . Utiliza una combinación de instrumentación del agente de Java y subclases para permitir la simulación de estos tipos. Como esto funciona de manera diferente a nuestro mecanismo actual y este tiene diferentes limitaciones y como queremos recabar experiencia y comentarios de los usuarios, esta característica debe activarse explícitamente para estar disponible; se puede hacer a través del mecanismo de extensión de mockito creando el archivo src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker contiene una sola línea:

mock-maker-inline

Después de crear este archivo, Mockito usará automáticamente este nuevo motor y se puede hacer:

final class FinalClass { final String finalMethod() { return "something"; } } FinalClass concrete = new FinalClass(); FinalClass mock = mock(FinalClass.class); given(mock.finalMethod()).willReturn("not anymore"); assertThat(mock.finalMethod()).isNotEqualTo(concrete.finalMethod());

En posteriores hitos, el equipo aportará una forma programática de usar esta función. Identificaremos y brindaremos asistencia para todos los escenarios inamovibles. Estén atentos y háganos saber lo que piensa de esta función.


Las clases / métodos finales / estáticos de burla solo son posibles con Mockito v2.

Esto no es posible con Mockito v1, de Mockito FAQ :

Cuáles son las limitaciones de Mockito

  • Necesita Java 1.5+

  • No se puede burlar de las clases finales

...