Cómo burlarse de una clase final con mockito


218

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 Seasons.javaporque quiero burlarme de la RainOnTreesclase. ¿Cómo puedo hacer esto con Mockito?


99
Mockito no lo permite, sin embargo, PowerMock sí.
fge

1
A partir de Mockito 2.x, Mockito ahora admite la burla de las clases y métodos finales.
Kent

Posible duplicado de Mock final class con Mockito 2
eliasah

Respuestas:


155

Es posible burlarse de clases / métodos finales / estáticos solo con Mockito v2.

agregue esto en su archivo gradle:

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

Esto no es posible con Mockito v1, de las preguntas frecuentes de Mockito :

¿Cuáles son las limitaciones de Mockito?

  • Necesita Java 1.5+

  • No puedo burlarme de las clases finales

...


2
Esto no funcionó para mí en Scala (con modificaciones de sbt).
micseydel

2
Esto no fue suficiente para mí. También tuve que crear src / test / resources / mockito-extensions / org.mockito.plugins.MockMaker con "mock-maker-inline" según baeldung.com/mockito-final
micseydel

204

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

Pero por ahora esa es una característica de "incubación". Se requieren algunos pasos para activarlo que se describen en Novedades de Mockito 2 :

Burlarse de las clases y métodos finales es una función de incubación , opcional. Utiliza una combinación de instrumentación de agentes Java y subclases para permitir la simulación de estos tipos. Como esto funciona de manera diferente a nuestro mecanismo actual y este tiene limitaciones diferentes y deseamos recopilar experiencia y comentarios de los usuarios, esta característica tuvo que activarse explícitamente para estar disponible; Se puede hacer a través del mecanismo de extensión mockito creando el archivo que src/test/resources/mockito-extensions/org.mockito.plugins.MockMakercontiene una sola línea:

mock-maker-inline

Después de crear este archivo, Mockito usará automáticamente este nuevo motor y uno 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 hitos posteriores, el equipo traerá una forma programática de usar esta función. Identificaremos y brindaremos soporte para todos los escenarios inamovibles. ¡Estén atentos y háganos saber lo que piensa de esta función!


14
Todavía recibo un error: No puedo burlarme / espiar clase android.content.ComponentName Mockito no puede burlarse / espiar porque: - clase final
IgorGanapolsky

3
Asegúrese de colocar el org.mockito.plugins.MockMakerarchivo en la carpeta correcta.
WindRider

77
También recibo el error incluso después de seguir lo mencionado anteriormente: Mockito no puede burlarse / espiar porque: - clase final
rcde0

8
@vCillusion esta respuesta no está relacionada con PowerMock de ninguna manera.
Línea del

66
Seguí estas instrucciones pero aún no podía hacer que esto funcionara, ¿alguien tenía que hacer algo más?
Franco

43

No puedes burlarte de una clase final con Mockito, ya que no puedes hacerlo solo.

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

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 mucho código repetitivo; La ventaja es que puede agregar algunos métodos que pueden estar relacionados con su negocio de aplicaciones (como el getInstance que está tomando un usuario en lugar de un AccessToken, en el caso anterior).

En su caso, crearía una RainOnTreesclase no final que delegue a la clase final. O, si puede hacerlo no final, sería mejor.


66
+1. Si lo desea, puede usar algo como Lombok's @Delegatepara manejar una gran cantidad de repeticiones.
ruakh

2
@luigi, ¿puede agregar un fragmento de código para Junit como ejemplo? Traté de crear Wrapper para mi clase final, pero no pude averiguar cómo probarlo.
Increíble

31

agregue esto en su archivo gradle:

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

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


1
Probablemente debería usar "testImplementation" ahora en lugar de "testCompile". A Gradle ya no le gusta "testCompile".
jwehrle

gran comentario, gracias! editado para testImplementation. comentario original: testCompile 'org.mockito: mockito-inline: 2.13.0'
BennyP

2
Esto da como resultado un error cuando se ejecuta en Linux / OpenJDK 1.8:org.mockito.exceptions.base.MockitoInitializationException: Could not initialize inline Byte Buddy mock maker. (This mock maker is not supported on Android.)
naXa

Funciona bien cuando se cambia a Oracle JDK 1.8
naXa

23

Utiliza Powermock. Este enlace muestra cómo hacerlo: https://github.com/jayway/powermock/wiki/MockFinal


30
Creo que PowerMock es como uno de esos medicamentos que solo deberían salir en base a "prescripción". En el sentido de: uno debe dejar muy claro que PowerMock tiene muchos problemas; y que usarlo es como el último último recurso; y debe evitarse tanto como sea posible.
GhostCat

1
¿Por qué dices eso?
PragmaticProgrammer

Estaba usando Powermockpara burlarme de las clases finales y los métodos estáticos para aumentar mi cobertura que se verificó oficialmente Sonarqube. La cobertura fue del 0% desde SonarQube, por cualquier razón no reconoce las clases que usan Powermock en cualquier lugar dentro de ella. A mi equipo y a mí nos llevó bastante tiempo darme cuenta de ello a través de algún hilo en línea. Esa es solo una de las razones para tener cuidado con Powermock y probablemente no lo use.
amer

16

Solo para seguir. 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 los dos trabaja.


1
Para agregar a esto, una cosa que observé fue que si está usando Powermock junto con mockito; luego agregar el archivo del complemento mockmaker en 'src / test / resources / mockito-extensions / org.mockito.plugins.MockMaker' no sería útil para burlarse de las clases finales. En cambio, agregar una dependencia como la mencionada anteriormente por Michael_Zhang resolvería el problema de burlarse de las clases finales. Además, asegúrese de estar usando Mockito 2 en lugar de Mockito1
dishooom

12

Supongo que lo hiciste finalporque quieres evitar que se extiendan otras clases RainOnTrees. Como sugiere Java Efectivo (ítem 15), hay otra forma de mantener una clase cerca para la extensión sin hacerlo final:

  1. Eliminar la finalpalabra clave;

  2. Haz su constructor private. Ninguna clase podrá extenderlo porque no podrá llamar al superconstructor;

  3. Cree un método de fábrica estático para crear instancias de su 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á usar Mockito y mantener su clase cerrada por extensión con un pequeño código repetitivo.


1
esto no funciona para los métodos finales que con mockito 2 también se pueden burlar.
Łukasz Rzeszotarski

11

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


2
Absolutamente, ¿por qué burlarse de una clase simple? El imitar es para las interacciones de los caros ': otros servicios, motores, etc. clases de datos
Striplight

3
Si crea una instancia de eso, no puede aplicar los métodos Mockito.verify después. El uso principal de los simulacros es poder probar algunos de sus métodos.
riroo

6

Prueba esto:

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

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

Para más detalles mira aquí


3
Cabe señalar que los delegados son muy diferentes de las burlas de espías nativos. En un espía mockito nativo, "this" en la referencia de instancia al espía mismo (porque es una subclase de uso). Sin embargo, en delegado, "this" será el objeto real someInstanceThatIsNotMockableOrSpyable. No el espía. Por lo tanto, no hay forma de volver / verificar las funciones de auto llamada.
Dennis C

1
puedes poner un ejemplo?
Vishwa Ratna

5

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


5

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

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

Por favor, recuerde el ítem 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();

5

En Mockito 3 y más, tengo el mismo problema y lo solucioné desde este enlace

Simulacros de clases y métodos finales con Mockito de la siguiente manera

Antes de que Mockito pueda usarse para burlarse de las clases y métodos finales, debe> configurarse.

Necesitamos agregar un archivo de texto al directorio src / test / resources / mockito-extensiones del proyecto llamado org.mockito.plugins.MockMaker y agregar una sola línea de texto:

mock-maker-inline

Mockito busca en el directorio de extensiones los archivos de configuración cuando se carga. Este archivo permite la burla de los métodos y clases finales.


4

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

Modifica tu archivo gradle. Tomemos un ejemplo desde 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 abrirla para la prueba:

@OpenForTesting
class RepoRepository 

Esto funciona bien en el nivel de aplicación build.gradle, pero ¿qué podemos hacer para obtener esto en el nivel de biblioteca?
Sumit T

¿Puedes elaborar un poco? Por lo general, use el patrón de fachada para conectarse a las bibliotecas. Y burlarse de estas clases de fachada para probar la aplicación. De esta manera, no necesitamos burlarnos de ninguna clase de lib.
Ozeetee

3

Esto se puede hacer si está utilizando 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-extensiones". Esta carpeta debe estar disponible en el classpath.
2. El contenido del archivo creado anteriormente debe ser una sola línea como se indica a continuación:
simulacro-maker-inline

Los dos pasos anteriores son necesarios para activar el mecanismo de extensión mockito y utilizar esta función de suscripció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í burlándose de lo inamovible .


Debe incluir la respuesta aquí y no vincular a un sitio externo. Si el procedimiento es largo, puede incluir una descripción general.
rghome

asegúrese de utilizar las anotaciones a continuación cuando se burlen de @RunWith (PowerMockRunner.class) @PrepareForTest ({AFinalClass.class})
vCillusion

1
@vCillusion: el ejemplo que he mostrado solo usa la API de Mockito2. Usando la función de aceptación de Mockito2, uno puede burlarse de las clases finales directamente sin la necesidad de usar Powermock.
ksl

2

Sí, el mismo problema aquí, no podemos burlarnos de una clase final con Mockito. Para ser exactos, Mockito no puede burlarse / espiar lo siguiente:

  • clases finales
  • clases anonimas
  • tipos primitivos

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


2

Creo que necesitas pensar más en principio. En cambio, tu clase final usa su interfaz y su simulacro de interfaz.

Para esto:

 public class RainOnTrees{

   fun startRain():Observable<Boolean>{

        // some code here
   }
}

añadir

interface iRainOnTrees{
  public void startRain():Observable<Boolean>
}

y burlarse de su interfaz:

 @Before
    fun setUp() {
        rainService= Mockito.mock(iRainOnTrees::class.java)

        `when`(rainService.startRain()).thenReturn(
            just(true).delay(3, TimeUnit.SECONDS)
        )

    }

1

Por favor mira a JMockit . Tiene una amplia documentación con muchos ejemplos. Aquí tiene una solución de ejemplo de su problema (para simplificar, he agregado un constructor Seasonspara inyectar una RainOnTreesinstancia simulada ):

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

    }
}

1

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

Aunque Mockito no puede , por diseño, simular 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 beneficios ).
  2. Estás probando la posibilidad de una decoración 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, su prueba también puede demostrar que el usuario solo puede decorar la API en lugar de extenderla.

En una nota más subjetiva: prefiero mantener los marcos al mínimo, por eso JUnit y Mockito suelen ser suficientes para mí. De hecho, restringir este camino a veces me obliga a refactorizar para siempre.


1

Si intenta ejecutar la prueba unitaria en la carpeta de prueba , la solución principal está bien. Solo síguelo añadiendo una extensión.

Pero si desea ejecutarlo con una clase relacionada con Android como contexto o actividad que se encuentra en la carpeta androidtest , la respuesta es para usted.


1

Agregue estas dependencias para ejecutar mockito con éxito:

testImplementation 'org.mockito: mockito-core: 2.24.5'
testImplementation "org.mockito: mockito-inline: 2.24.5"


0

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

Por otro lado, si eres el único que marca las clases finales, deja de hacerlo. Me encontré con esta pregunta porque estoy trabajando con una API donde todo se marcó como final para evitar mi legítima necesidad de extensión (burla), y deseo que el desarrollador no haya asumido que nunca necesitaría extender la clase.


1
Las clases API públicas deben estar abiertas para la extensión. Totalmente de acuerdo. Sin embargo, en una base de código privada, finaldebería ser el valor predeterminado.
ErikE

0

Para nosotros, fue porque excluimos mockito-inline de koin-test. Un módulo de Gradle realmente necesitaba esto y por razón solo falló en las versiones de lanzamiento (las versiones de depuración en el IDE funcionaron) :-P


0

Para la clase final, agregue a continuación para simular y llamar estática o no estática.

1- agregue esto en el nivel de clase @SuppressStatucInitializationFor (value = {class name with package})
2- PowerMockito.mockStatic (classname.class) se burlará de la clase
3- y luego usará su instrucción when para devolver el objeto simulado cuando llame al método de esta clase.

Disfrutar


-5

No intenté final, pero en privado, usando la reflexión, elimine el modificador funcionó He comprobado más, no funciona para final.


esto no responde a la pregunta que se hace
Sanjit Kumar Mishra
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.