2011-05-07 7 views
29

tengo una clase de legado que contiene un nuevo() para crear una instancia de un LoginContext():clase de prueba con una nueva llamada() en ella con Mockito

public class TestedClass { 
    public LoginContext login(String user, String password) { 
    LoginContext lc = new LoginContext("login", callbackHandler); 
    } 
} 

Quiero probar esta clase mediante Mockito a simular el LoginContext ya que requiere que el material de seguridad JAAS se configure antes de crear instancias, pero no estoy seguro de cómo hacerlo sin cambiar el método de inicio de sesión() para externalizar el LoginContext. ¿Es posible usar Mockito para burlarse de la clase LoginContext?

Respuesta

34

Para el futuro se lo recomendaría Eran Harel respuesta (refactorización movimiento new a la fábrica que puede ser burlado). Pero si no desea cambiar el código fuente original, use la función muy útil y única: espias. Desde documentation:

Puede crear espías de objetos reales. Cuando utiliza el espía, se llaman a los métodos reales (a menos que se haya troquelado un método).

Los espías reales se deben utilizar cuidadosamente y ocasionalmente, por ejemplo cuando se trata de código heredado.

En su caso, usted debe escribir:

TestedClass tc = spy(new TestedClass()); 
LoginContext lcMock = mock(LoginContext.class); 
when(tc.login(anyString(), anyString())).thenReturn(lcMock); 
+0

Esto era exactamente lo que estaba buscando, gracias Tomasz. La parte extra en la parte inferior del doco sobre doReturn fue especialmente útil. – bwobbones

+0

ninguna alternativa para las versiones menos de 1,8 – pseudoCoder

+0

Sí, el uso de una fábrica que puede ser burlado es la respuesta real aquí. Usar spy es controvertido y evitar IMO. – dhaag23

2

No, que yo sepa, pero lo que trata de hacer algo como esto cuando se crea una instancia de TestedClass que desea probar:

TestedClass toTest = new TestedClass() { 
    public LoginContext login(String user, String password) { 
     //return mocked LoginContext 
    } 
}; 

Otra opción sería utilizar Mockito para crear una instancia de TestedClass y permita que la instancia simulada devuelva un LoginContext.

16

Puede usar una fábrica para crear el contexto de inicio de sesión. Entonces puede burlarse de la fábrica y devolver lo que quiera para su prueba.

public class TestedClass { 
    private final LoginContextFactory loginContextFactory; 

    public TestedClass(final LoginContextFactory loginContextFactory) { 
    this.loginContextFactory = loginContextFactory; 
    } 

    public LoginContext login(String user, String password) { 
    LoginContext lc = loginContextFactory.createLoginContext(); 
    } 
} 

public interface LoginContextFactory { 
    public LoginContext createLoginContext(); 
} 
+0

En este caso, tendré que crear una implementación de LoginContextFactory que solo devuelva un nuevo objeto de LoginContext? ¿Es eso correcto? – user1692342

+1

Esto requerirá que cada clase que tengo necesite tener una fábrica. Para POJOs, creo que es excesivo crear fábricas de este tipo. ¿Hay otras alternativas de un marco de prueba? – alltej

+0

@alltej solo necesita usar fábricas en las clases que crean instancias de objetos nuevos, ¿no es cierto que no todos los pojo? – Adam

21

estoy a favor de la solución de Eran Harel y en los casos que no sea posible, la sugerencia de Tomasz Nurkiewicz por espionaje es excelente. Sin embargo, vale la pena señalar que hay situaciones en las que ninguno se aplicaría. P.ej. si el método login era un poco "más robusto":

public class TestedClass { 
    public LoginContext login(String user, String password) { 
     LoginContext lc = new LoginContext("login", callbackHandler); 
     lc.doThis(); 
     lc.doThat(); 
    } 
} 

... y esto era viejo código que no pudo ser rediseñado para extraer la inicialización de una nueva LoginContext a su propio método y aplicar una de las soluciones mencionadas anteriormente .

Para mayor completitud, vale la pena mencionar una tercera técnica: usar PowerMock para inyectar el objeto simulado cuando se llama al operador new. Sin embargo, PowerMock no es una bala de plata. Funciona aplicando manipulación de código byte en las clases en las que se burla, lo que podría ser una práctica dudosa si las clases probadas emplean manipulación o reflexión de código de bytes y, al menos desde mi experiencia personal, se ha sabido que introducen un impacto de rendimiento en la prueba.Por otra parte, si no hay otras opciones, la única opción que tiene que ser la buena opción:

@RunWith(PowerMockRunner.class) 
@PrepareForTest(TestedClass.class) 
public class TestedClassTest { 

    @Test 
    public void testLogin() { 
     LoginContext lcMock = mock(LoginContext.class); 
     whenNew(LoginContext.class).withArguments(anyString(), anyString()).thenReturn(lcMock); 
     TestedClass tc = new TestedClass(); 
     tc.login ("something", "something else"); 
     // test the login's logic 
    } 
} 
+4

+1 para establecer el escenario que generalmente es el caso, ya que la mayoría de los métodos que lo necesitan y donde la refactorización no es posible/difícil es el código antiguo/heredado. – Pankaj

+0

el gran problema con este enfoque es que usted tiene que incluir el 'TestedClass' en el' @ PrepareForTest' que hace de forma automática toda la cobertura de la clase siendo 0 tanto en sonar y EclEmma. Esto podría deberse a un error en una de las herramientas de análisis de cobertura – ACV

+1

'si no hay otras opciones, la única opción debe ser la buena opción' +1 para el Sr. Sherlock Holmes. ;) – Shane

2
public class TestedClass { 
    public LoginContext login(String user, String password) { 
     LoginContext lc = new LoginContext("login", callbackHandler); 
     lc.doThis(); 
     lc.doThat(); 
    } 
    } 

- Clase de prueba:

@RunWith(PowerMockRunner.class) 
    @PrepareForTest(TestedClass.class) 
    public class TestedClassTest { 

     @Test 
     public void testLogin() { 
      LoginContext lcMock = mock(LoginContext.class); 
      whenNew(LoginContext.class).withArguments(anyString(), anyString()).thenReturn(lcMock); 
//comment: this is giving mock object (lcMock) 
      TestedClass tc = new TestedClass(); 
      tc.login ("something", "something else"); /// testing this method. 
      // test the login's logic 
     } 
    } 

Al llamar al método actual de la tc.login ("something", "something else"); TESTLOGIN() { - Este LoginContext lc se establece en nulo y tirando NPE mientras se llama lc.doThis();

+0

Esto funciona. ¡PowerMockito es el mejor! – NMSL

0

En situaciones donde la clase bajo te st puede ser modificado y cuando es deseable evitar la manipulación de código de bytes, para mantener las cosas rápido o para minimizar las dependencias de terceros, que aquí es mi opinión sobre el uso de una fábrica para extraer la operación new.

public class TestedClass { 

    interface PojoFactory { Pojo getNewPojo(); } 

    private final PojoFactory factory; 

    /** For use in production - nothing needs to change. */ 
    public TestedClass() { 
     this.factory = new PojoFactory() { 
      @Override 
      public Pojo getNewPojo() { 
       return new Pojo(); 
      } 
     }; 
    } 

    /** For use in testing - provide a pojo factory. */ 
    public TestedClass(PojoFactory factory) { 
     this.factory = factory; 
    } 

    public void doSomething() { 
     Pojo pojo = this.factory.getNewPojo(); 
     anythingCouldHappen(pojo); 
    } 
} 

Con esto en su lugar, sus pruebas, afirma y verificar las llamadas en el objeto Pojo son fáciles:

public void testSomething() { 
    Pojo testPojo = new Pojo(); 
    TestedClass target = new TestedClass(new TestedClass.PojoFactory() { 
       @Override 
       public Pojo getNewPojo() { 
        return testPojo; 
       } 
      }); 
    target.doSomething(); 
    assertThat(testPojo.isLifeStillBeautiful(), is(true)); 
} 

La única desventaja de este enfoque potencialmente surge si TestClass tiene varios constructores que se había tiene que duplicar con el parámetro extra.

Por razones sólidas que probablemente desea poner la interfaz PojoFactory a la clase POJO lugar, y la fábrica de producción así.

public class Pojo { 

    interface PojoFactory { Pojo getNewPojo(); } 

    public static final PojoFactory productionFactory = 
     new PojoFactory() { 
      @Override 
      public Pojo getNewPojo() { 
       return new Pojo(); 
      } 
     }; 
Cuestiones relacionadas