Clase de prueba con una nueva () llamada con Mockito

Tengo una clase heredada que contiene una llamada nueva () para crear una instancia de un LoginContext ():

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

Quiero probar esta clase usando Mockito para simular el LoginContext, ya que requiere que se configure el material de seguridad JAAS 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?

Para el futuro, recomendaría la respuesta de Eran Harel (refactorización de mudarme a la fábrica que se puede burlar). Pero si no desea cambiar el código fuente original, use una función muy útil y única: espías . De la documentación :

Puedes crear espías de objetos reales. Cuando utilizas el espía, se llaman a los métodos reales (a menos que se haya tropezado con un método).

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

En tu caso deberías escribir:

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

Estoy totalmente de acuerdo con la solución de Eran Harel y, en casos que no es posible, la sugerencia de Tomasz Nurkiewicz para espiar es excelente. Sin embargo, vale la pena señalar que hay situaciones en las que ninguno se aplicaría. Por ejemplo, si el método de login fue 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 este era un código antiguo que no podía ser refactorizado para extraer la inicialización de un nuevo LoginContext a su propio método y aplicar una de las soluciones antes mencionadas.

Para mayor completitud, vale la pena mencionar una tercera técnica: usar PowerMock para inyectar el objeto simulado cuando se llame al new operador. 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 debe 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 } } 

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

No es que yo sepa, pero ¿qué tal hacer algo como esto cuando creas una instancia de TestedClass que quieres probar?

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

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

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

Cuando se llama al método real tc.login ("something", "something else"); from testLogin () {- Este LoginContext lc se establece en nulo y lanza NPE mientras se llama a lc.doThis();

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

 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, su prueba, afirma y verifica 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)); } 

El único inconveniente de este enfoque surge potencialmente si TestClass tiene múltiples constructores que tendría que duplicar con el parámetro adicional.

Por razones SÓLIDAS, probablemente quieras poner la interfaz de PojoFactory en la clase Pojo, y también en la fábrica de producción.

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