page - mockito java ejemplo
Variables de miembros burlones de una clase usando Mockito (7)
Debe proporcionar una forma de acceder a las variables miembro para que pueda pasar una simulación (las formas más comunes serían un método setter o un constructor que toma un parámetro).
Si su código no proporciona una forma de hacerlo, se considera incorrectamente para TDD (Desarrollo controlado por prueba).
Soy un novato en desarrollo y pruebas unitarias en particular. Supongo que mi requisito es bastante simple, pero estoy interesado en conocer otras opiniones sobre esto.
Supongamos que tengo dos clases como tal -
public class First {
Second second ;
public First(){
second = new Second();
}
public String doSecond(){
return second.doSecond();
}
}
class Second {
public String doSecond(){
return "Do Something";
}
}
Digamos que estoy escribiendo una prueba unitaria para probar el método First.doSecond()
. Sin embargo, supongamos que quiero Second.doSecond()
class como tal. Estoy usando Mockito para hacer esto.
public void testFirst(){
Second sec = mock(Second.class);
when(sec.doSecond()).thenReturn("Stubbed Second");
First first = new First();
assertEquals("Stubbed Second", first.doSecond());
}
Estoy viendo que la burla no tiene efecto y la afirmación falla. ¿No hay forma de burlarse de las variables miembro de una clase que quiero probar? ?
Esto no es posible si no puedes cambiar tu código. Pero me gusta la inyección de dependencia y Mockito lo admite:
public class First {
@Resource
Second second;
public First() {
second = new Second();
}
public String doSecond() {
return second.doSecond();
}
}
Su prueba:
@RunWith(MockitoJUnitRunner.class)
public class YourTest {
@Mock
Second second;
@InjectMocks
First first = new First();
public void testFirst(){
when(second.doSecond()).thenReturn("Stubbed Second");
assertEquals("Stubbed Second", first.doSecond());
}
}
Esto es muy agradable y fácil.
Muchos otros ya te han aconsejado que reconsideres tu código para que sea más comprobable, un buen consejo y, por lo general, más simple de lo que estoy por sugerir.
Si no puede cambiar el código para hacerlo más comprobable, PowerMock: https://code.google.com/p/powermock/
PowerMock amplía Mockito (para que no tenga que aprender un nuevo marco simulado), proporcionando funcionalidad adicional. Esto incluye la capacidad de hacer que un constructor devuelva un simulacro. Potente, pero un poco complicado, así que úsalo juiciosamente.
Usas un corredor falso diferente. Y debe preparar la clase que invocará al constructor. (Tenga en cuenta que este es un problema común: prepare la clase que llama al constructor, no a la clase construida)
@RunWith(PowerMockRunner.class)
@PrepareForTest({First.class})
Luego, en la configuración de prueba, puede usar el método whenNew para que el constructor devuelva un simulacro
whenNew(Second.class).withAnyArguments().thenReturn(mock(Second.class));
Sí, esto se puede hacer, como se muestra en la siguiente prueba (escrito con la API de simulación JMockit, que desarrollo):
@Test
public void testFirst(@Mocked final Second sec) {
new NonStrictExpectations() {{ sec.doSecond(); result = "Stubbed Second"; }};
First first = new First();
assertEquals("Stubbed Second", first.doSecond());
}
Con Mockito, sin embargo, tal prueba no se puede escribir. Esto se debe a la forma en que se implementa la burla en Mockito, donde se crea una subclase de la clase a burlarse; solo las instancias de esta subclase "simulada" pueden tener un comportamiento burlado, por lo que debe hacer que el código probado las use en lugar de cualquier otra instancia.
Si miras detenidamente tu código, verás que la second
propiedad en tu prueba sigue siendo una instancia de Second
, no un simulacro (no pasas el simulacro al first
en tu código).
La forma más simple sería crear un setter para el second
en First
Class y pasarlo simuladamente.
Me gusta esto:
public class First {
Second second ;
public First(){
second = new Second();
}
public String doSecond(){
return second.doSecond();
}
public void setSecond(Second second) {
this.second = second;
}
}
class Second {
public String doSecond(){
return "Do Something";
}
}
....
public void testFirst(){
Second sec = mock(Second.class);
when(sec.doSecond()).thenReturn("Stubbed Second");
First first = new First();
first.setSecond(sec)
assertEquals("Stubbed Second", first.doSecond());
}
Otro sería pasar una Second
instancia como First
parámetro constructor.
Si no puede modificar el código, creo que la única opción sería usar la reflexión:
public void testFirst(){
Second sec = mock(Second.class);
when(sec.doSecond()).thenReturn("Stubbed Second");
First first = new First();
Field privateField = PrivateObject.class.
getDeclaredField("second");
privateField.setAccessible(true);
privateField.set(first, sec);
assertEquals("Stubbed Second", first.doSecond());
}
Pero probablemente puedas, ya que es raro hacer pruebas en código que no controlas (aunque uno puede imaginar un escenario donde tienes que probar una biblioteca externa porque su autor no lo hizo :))
Si no puede cambiar la variable miembro, entonces al revés, use powerMockit y llame
Second second = mock(Second.class)
when(second.doSecond()).thenReturn("Stubbed Second");
whenNew(Second.class).withAnyArguments.thenReturn(second);
Ahora el problema es que CUALQUIER llamada al nuevo Segundo devolverá la misma instancia simulada. Pero en tu caso simple, esto funcionará.
Tuve el mismo problema cuando no se estableció un valor privado porque Mockito no llama a super constructores. Aquí es cómo aumento la burla con reflexión.
Primero, creé una clase TestUtils que contiene muchas utilidades útiles que incluyen estos métodos de reflexión. El acceso de reflexión es un poco inestable para implementar cada vez. Creé estos métodos para probar código en proyectos que, por una razón u otra, no tenían ningún paquete de burla y no me invitaron a incluirlo.
public class TestUtils {
// get a static class value
public static Object reflectValue(Class<?> classToReflect, String fieldNameValueToFetch) {
try {
Field reflectField = reflectField(classToReflect, fieldNameValueToFetch);
reflectField.setAccessible(true);
Object reflectValue = reflectField.get(classToReflect);
return reflectValue;
} catch (Exception e) {
fail("Failed to reflect "+fieldNameValueToFetch);
}
return null;
}
// get an instance value
public static Object reflectValue(Object objToReflect, String fieldNameValueToFetch) {
try {
Field reflectField = reflectField(objToReflect.getClass(), fieldNameValueToFetch);
Object reflectValue = reflectField.get(objToReflect);
return reflectValue;
} catch (Exception e) {
fail("Failed to reflect "+fieldNameValueToFetch);
}
return null;
}
// find a field in the class tree
public static Field reflectField(Class<?> classToReflect, String fieldNameValueToFetch) {
try {
Field reflectField = null;
Class<?> classForReflect = classToReflect;
do {
try {
reflectField = classForReflect.getDeclaredField(fieldNameValueToFetch);
} catch (NoSuchFieldException e) {
classForReflect = classForReflect.getSuperclass();
}
} while (reflectField==null || classForReflect==null);
reflectField.setAccessible(true);
return reflectField;
} catch (Exception e) {
fail("Failed to reflect "+fieldNameValueToFetch +" from "+ classToReflect);
}
return null;
}
// set a value with no setter
public static void refectSetValue(Object objToReflect, String fieldNameToSet, Object valueToSet) {
try {
Field reflectField = reflectField(objToReflect.getClass(), fieldNameToSet);
reflectField.set(objToReflect, valueToSet);
} catch (Exception e) {
fail("Failed to reflectively set "+ fieldNameToSet +"="+ valueToSet);
}
}
}
Entonces puedo probar la clase con una variable privada como esta. Esto es útil para burlarse de los árboles profundos en clase que tampoco tienes control.
@Test
public void testWithRectiveMock() throws Exception {
// mock the base class using Mockito
ClassToMock mock = Mockito.mock(ClassToMock.class);
TestUtils.refectSetValue(mock, "privateVariable", "newValue");
// and this does not prevent normal mocking
Mockito.when(mock.somthingElse()).thenReturn("anotherThing");
// ... then do your asserts
}
Modifiqué mi código de mi proyecto real aquí, en la página. Podría haber una cuestión de compilación o dos. Creo que entiendes la idea general. Siéntase libre de tomar el código y usarlo si lo encuentra útil.