Probando el método privado usando mockito

104
clase pública A {

    método de vacío público (booleano b) {
          si (b == verdadero)
               Método 1();
          más
               método2 ();
    }

    método vacío privado1 () {}
    método vacío privado2 () {}
}
clase pública TestA {

    @Prueba
    testMethod de vacío público () {
      A a = simulacro (A.clase);
      un método (verdadero);
      // cómo probar como verify (a) .method1 ();
    }
}

¿Cómo probar el método privado se llama o no, y cómo probar el método privado usando mockito?

Nageswaran
fuente
A pesar de ser específico de mockito , esta es esencialmente la misma pregunta que ¿Cómo
Raedwald

Respuestas:

81

No puede hacer eso con Mockito, pero puede usar Powermock para extender Mockito y simular métodos privados. Powermock es compatible con Mockito. He aquí un ejemplo.

shift66
fuente
19
Estoy confundido con esta respuesta. Esto es una burla, pero el título está probando los métodos privados
diyoda_
He usado Powermock para simular el método privado, pero ¿cómo puedo probar el método privado con Powermock? ¿Dónde puedo pasar alguna entrada y esperar alguna salida del método y luego verificar la salida?
Rito
No puedes. Te burlas de la entrada y salida, no puedes probar la funcionalidad real.
Talha
131

No es posible a través de mockito. De su wiki

¿Por qué Mockito no se burla de los métodos privados?

En primer lugar, no somos dogmáticos sobre burlarnos de los métodos privados. Simplemente no nos importan los métodos privados porque desde el punto de vista de probar los métodos privados no existen. Aquí hay un par de razones por las que Mockito no se burla de los métodos privados:

Requiere pirateo de cargadores de clases que nunca es a prueba de balas y cambia la API (debe usar un corredor de prueba personalizado, anotar la clase, etc.).

Es muy fácil solucionarlo: simplemente cambie la visibilidad del método de privado a protegido por paquete (o protegido).

Requiere que dedique tiempo a implementarlo y mantenerlo. Y no tiene sentido dado el punto # 2 y un hecho que ya está implementado en otra herramienta (powermock).

Finalmente ... Burlarse de los métodos privados es una pista de que hay algo mal en la comprensión de OO. En OO quieres que los objetos (o roles) colaboren, no métodos. Olvídese de pascal y el código de procedimiento. Piense en objetos.

Aravind Yarram
fuente
1
Hay una suposición fatal hecha por esa declaración:> Burlarse de los métodos privados es una pista de que hay algo mal en la comprensión de OO. Si estoy probando un método público y llama a métodos privados, me gustaría simular los retornos de métodos privados. Seguir el supuesto anterior obvia la necesidad de implementar métodos privados. ¿Cómo es eso una mala comprensión de OO?
eggmatters
1
@eggmatters Según Baeldung "Las técnicas de burla deben aplicarse a las dependencias externas de la clase y no a la clase en sí. Si la burla de métodos privados es esencial para probar nuestras clases, generalmente indica un mal diseño". Aquí hay un hilo interesante al respecto softwareengineering.stackexchange.com/questions/100959/…
Jason Glez
34

Aquí hay un pequeño ejemplo de cómo hacerlo con powermock.

public class Hello {
    private Hello obj;
    private Integer method1(Long id) {
        return id + 10;
    }
} 

Para probar el método 1 use el código:

Hello testObj = new Hello();
Integer result = Whitebox.invokeMethod(testObj, "method1", new Long(10L));

Para establecer el objeto privado obj use esto:

Hello testObj = new Hello();
Hello newObject = new Hello();
Whitebox.setInternalState(testObj, "obj", newObject);
Mindaugas Jaraminas
fuente
Su enlace solo apunta al repositorio simulado de energía @Mindaugas
Xavier
@Xavier verdad. Puedes usarlo si quieres en tu proyecto.
Mindaugas Jaraminas
1
Maravilloso !!! muy bien explicado con estos simples ejemplos casi todo :) Porque el propósito es solo probar el código y no lo que todo el marco proporciona :)
siddhusingh
Actualice esto. Whitebox ya no forma parte de la API pública.
user447607
17

Piense en esto en términos de comportamiento, no en términos de los métodos que existen. El método llamado methodtiene un comportamiento particular si bes verdadero. Tiene un comportamiento diferente si bes falso. Esto significa que debe escribir dos pruebas diferentes para method; uno para cada caso. Entonces, en lugar de tener tres pruebas orientadas a métodos (una para method, una para method1, una para method2, tiene dos pruebas orientadas al comportamiento.

Relacionado con esto (sugerí esto en otro hilo de SO recientemente, y como resultado me llamaron una palabra de cuatro letras, así que siéntase libre de tomar esto con un grano de sal); Encuentro útil elegir nombres de prueba que reflejen el comportamiento que estoy probando, en lugar del nombre del método. Así que no llame a sus pruebas testMethod(), testMethod1(), testMethod2()y así sucesivamente. Me gustan los nombres como calculatedPriceIsBasePricePlusTax()o taxIsExcludedWhenExcludeIsTrue()que indiquen qué comportamiento estoy probando; luego, dentro de cada método de prueba, pruebe solo el comportamiento indicado. La mayoría de estos comportamientos involucrarán solo una llamada a un método público, pero pueden involucrar muchas llamadas a métodos privados.

Espero que esto ayude.

Dawood ibn Kareem
fuente
13

Si bien Mockito no proporciona esa capacidad, puede lograr el mismo resultado usando Mockito + la clase JUnit ReflectionUtils o la clase Spring ReflectionTestUtils . Vea un ejemplo a continuación tomado de aquí que explica cómo invocar un método privado:

ReflectionTestUtils.invokeMethod(student, "saveOrUpdate", "From Unit test");

Se pueden encontrar ejemplos completos con ReflectionTestUtils y Mockito en el libro Mockito for Spring

AR1
fuente
ReflectionTestUtils.invokeMethod (estudiante, "saveOrUpdate", "argumento1", "argumento2", "argumento3"); El último argumento de invokeMethod, usa Vargs que puede tomar múltiples argumentos que deben pasarse al método privado. funciona.
Tim
Esta respuesta debería tener más votos a favor, con mucho la forma más fácil de probar métodos privados.
Max
9

Se supone que no debes probar métodos privados. Solo se deben probar los métodos no privados, ya que de todos modos deberían llamar a los métodos privados. Si "desea" probar métodos privados, puede indicar que necesita repensar su diseño:

¿Estoy usando la inyección de dependencia adecuada? ¿Es posible que deba mover los métodos privados a una clase separada y probar eso? ¿Deben estos métodos ser privados? ... ¿no pueden ser predeterminados o protegidos?

En el caso anterior, es posible que los dos métodos que se denominan "aleatoriamente" deban colocarse en una clase propia, probarse y luego inyectarse en la clase anterior.

Jaco Van Niekerk
fuente
26
Un punto válido. Sin embargo, ¿no es la razón para usar un modificador privado para los métodos porque simplemente desea cortar códigos que son demasiado largos y / o repetitivos? Separarlo como otra clase es como si estuvieras promocionando esas líneas de códigos para que sean ciudadanos de primera clase que no se reutilizarán en ningún otro lugar porque estaba destinado específicamente a dividir códigos largos y evitar la repetición de líneas de códigos. Si va a separarlo de otra clase, simplemente no se siente bien; fácilmente obtendrías una explosión de clase.
supertonsky
2
Supertonsky señaló, me refería al caso general. Estoy de acuerdo en que en el caso anterior no debería estar en una clase separada. (Sin embargo, haga +1 en su comentario; es un punto muy válido que está haciendo sobre la promoción de miembros privados)
Jaco Van Niekerk
4
@supertonsky, no he podido encontrar ninguna respuesta satisfactoria a este problema. Hay varias razones por las que podría usar miembros privados y, muy a menudo, no indican un olor a código y me beneficiaría enormemente de probarlos. La gente parece ignorar esto diciendo "simplemente no lo hagas".
LuddyPants
3
Lo siento, he optado por votar en contra en función de "Si 'desea' probar métodos privados, puede indicar que necesita votar en contra de su diseño". Está bien, es justo, pero una de las razones para realizar pruebas es porque, bajo una fecha límite en la que no tienes tiempo para repensar el diseño, estás tratando de implementar de manera segura un cambio que debe realizarse en un método privado. En un mundo ideal, ¿no sería necesario cambiar ese método privado porque el diseño sería perfecto? Claro, pero en un mundo perfecto, pero es discutible porque en un mundo perfecto que necesita pruebas, todo simplemente funciona. :)
John Lockwood
2
@Juan. Punto tomado, su voto negativo justificado (+1). Gracias por el comentario también. Estoy de acuerdo contigo en lo que dices. En tales casos, puedo ver una de dos opciones: o el método se convierte en paquete privado o protegido y las pruebas unitarias se escriben como de costumbre; o (y esto es irónico y una mala práctica) se escribe rápidamente un método principal para asegurarse de que todavía funciona. Sin embargo, mi respuesta se basó en un escenario de código NUEVO que se está escribiendo y no se refactoriza cuando es posible que no pueda alterar el diseño original.
Jaco Van Niekerk
6

Pude probar un método privado en el interior usando mockito usando la reflexión. Aquí está el ejemplo, intenté nombrarlo de tal manera que tenga sentido

//Service containing the mock method is injected with mockObjects

@InjectMocks
private ServiceContainingPrivateMethod serviceContainingPrivateMethod;

//Using reflection to change accessibility of the private method

Class<?>[] params = new Class<?>[]{PrivateMethodParameterOne.class, PrivateMethodParameterTwo.class};
    Method m = serviceContainingPrivateMethod .getClass().getDeclaredMethod("privateMethod", params);
    //making private method accessible
    m.setAccessible(true); 
    assertNotNull(m.invoke(serviceContainingPrivateMethod, privateMethodParameterOne, privateMethodParameterTwo).equals(null));
Abdullah Choudhury
fuente
6
  1. Al usar la reflexión, se pueden llamar a métodos privados desde clases de prueba. En este caso,

    // el método de prueba será así ...

    public class TestA {
    
      @Test
        public void testMethod() {
    
        A a= new A();
        Method privateMethod = A.class.getDeclaredMethod("method1", null);
        privateMethod.setAccessible(true);
        // invoke the private method for test
        privateMethod.invoke(A, null);
    
        }
    }
  2. Si el método privado llama a cualquier otro método privado, entonces debemos espiar el objeto y eliminar el otro método. La clase de prueba será como ...

    // el método de prueba será así ...

    public class TestA {
    
      @Test
        public void testMethod() {
    
        A a= new A();
        A spyA = spy(a);
        Method privateMethod = A.class.getDeclaredMethod("method1", null);
        privateMethod.setAccessible(true);
        doReturn("Test").when(spyA, "method2"); // if private method2 is returning string data
        // invoke the private method for test
        privateMethod.invoke(spyA , null);
    
        }
    }

** El enfoque consiste en combinar la reflexión y el espionaje del objeto. ** método1 y ** método2 son métodos privados y el método1 llama al método2.

Singh Arun
fuente
4

Realmente no entiendo tu necesidad de probar el método privado. La raíz del problema es que su método público ha sido nulo como tipo de retorno y, por lo tanto, no puede probar su método público. Por lo tanto, se ve obligado a probar su método privado. ¿Mi suposición es correcta?

Algunas posibles soluciones (AFAIK):

  1. Burlarse de sus métodos privados, pero aún así no estará probando "realmente" sus métodos.

  2. Verifique el estado del objeto utilizado en el método. LA MAYORÍA de los métodos procesan los valores de entrada y devuelven una salida, o cambian el estado de los objetos. También puede emplearse la prueba de los objetos para el estado deseado.

    public class A{
    
    SomeClass classObj = null;
    
    public void publicMethod(){
       privateMethod();
    }
    
    private void privateMethod(){
         classObj = new SomeClass();
    }
    
    }

    [Aquí puede probar el método privado, verificando el cambio de estado de classObj de nulo a no nulo].

  3. Refactorice un poco su código (espero que no sea un código heredado). Mi fundamento para escribir un método es que siempre se debe devolver algo (un int / a booleano). El valor devuelto PUEDE o NO PUEDE ser utilizado por la implementación, pero SEGURO SERÁ utilizado por la prueba

    código.

    public class A
    { 
        public int method(boolean b)
        {
              int nReturn = 0;
              if (b == true)
                   nReturn = method1();
              else
                   nReturn = method2();
        }
    
        private int method1() {}
    
        private int method2() {}
    
    }
Reji
fuente
3

En realidad, hay una forma de probar métodos de un miembro privado con Mockito. Digamos que tienes una clase como esta:

public class A {
    private SomeOtherClass someOtherClass;
    A() {
        someOtherClass = new SomeOtherClass();
    }
    public void method(boolean b){
        if (b == true)
            someOtherClass.method1();
        else
            someOtherClass.method2();
    }

}

public class SomeOtherClass {
    public void method1() {}
    public void method2() {}
}

Si desea probar a.methodse invocará un método desde SomeOtherClass, puede escribir algo como a continuación.

@Test
public void testPrivateMemberMethodCalled() {
    A a = new A();
    SomeOtherClass someOtherClass = Mockito.spy(new SomeOtherClass());
    ReflectionTestUtils.setField( a, "someOtherClass", someOtherClass);
    a.method( true );

    Mockito.verify( someOtherClass, Mockito.times( 1 ) ).method1();
}

ReflectionTestUtils.setField(); tropezará al miembro privado con algo que pueda espiar.

Fan Jin
fuente
2

Coloque su prueba en el mismo paquete, pero en una carpeta de origen diferente (src / main / java vs src / test / java) y haga que esos métodos sean privados del paquete. La comprobabilidad de la imitación es más importante que la privacidad.

Roland Schneider
fuente
6
La única razón legítima es probar una parte de un sistema heredado. Si comienza a probar el método privado / paquete-privado, expone las partes internas de su objeto. Hacerlo normalmente da como resultado un código refactorizable deficiente. Prefiera la composición para que pueda lograr la capacidad de prueba, con todas las bondades de un sistema orientado a objetos.
Brice
1
De acuerdo, esa sería la forma preferida. Sin embargo, si realmente desea probar métodos privados con mockito, esta es la única opción (segura) que tiene. Sin embargo, mi respuesta fue un poco apresurada, debería haber señalado los riesgos, como lo han hecho tú y los demás.
Roland Schneider
Esta es mi forma preferida. No hay nada de malo en exponer el objeto interno en el nivel de paquete privado; y la prueba unitaria es una prueba de caja blanca, necesita conocer el interno para realizar la prueba.
Andrew Feng
0

En los casos en que el método privado no es nulo y el valor de retorno se usa como parámetro para el método de una dependencia externa, puede simular la dependencia y usar un ArgumentCaptorpara capturar el valor de retorno. Por ejemplo:

ArgumentCaptor<ByteArrayOutputStream> csvOutputCaptor = ArgumentCaptor.forClass(ByteArrayOutputStream.class);
//Do your thing..
verify(this.awsService).uploadFile(csvOutputCaptor.capture());
....
assertEquals(csvOutputCaptor.getValue().toString(), "blabla");
Milensky
fuente