anand hemmig
Soy un novato en el desarrollo y en las pruebas unitarias en particular. Supongo que mi requisito es bastante simple, pero estoy ansioso por conocer los pensamientos de otros sobre esto.
Supongamos que tengo dos clases así:
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 prueba unitaria para probar First.doSecond()
método. Sin embargo, supongamos que quiero burlarme Second.doSecond()
clase así. 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 surte efecto y la afirmación falla. ¿No hay forma de burlarse de las variables miembro de una clase que quiero probar? ?
gatita
Debe proporcionar una forma de acceder a las variables miembro para que pueda pasar un simulacro (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 hacer esto, se factoriza incorrectamente para TDD (Desarrollo dirigido por pruebas).
-
Gracias. Yo lo veo. Solo me pregunto cómo puedo realizar pruebas de integración usando simulacros donde puede haber muchos métodos internos, clases que pueden necesitar ser simuladas, pero no necesariamente disponibles para configurarse a través de un setXXX() de antemano.
–Anand Hemmige
24 de enero de 2012 a las 23:18
-
Utilice un marco de inyección de dependencia, con una configuración de prueba. Dibuje un diagrama de secuencia de la prueba de integración que está tratando de hacer. Factorice el diagrama de secuencia en los objetos que realmente puede controlar. Esto significa que si está trabajando con una clase de marco que tiene el antipatrón de objeto dependiente que muestra arriba, entonces debe considerar el objeto y su miembro mal factorizado como una sola unidad en términos del diagrama de secuencia. Esté preparado para ajustar la factorización de cualquier código que controle, para que sea más comprobable.
– gatita
24 de enero de 2012 a las 23:24
-
Estimado @kittylyst, sí, probablemente esté mal desde el punto de vista de TDD o desde cualquier punto de vista racional. Pero a veces un desarrollador trabaja en lugares donde nada tiene sentido y el único objetivo que tiene es completar las historias que le han asignado y marcharse. Sí, está mal, no tiene sentido, gente no cualificada toma las decisiones clave y todo eso. Entonces, al final del día, los antipatrones ganan por mucho.
– amanas
07/04/2015 a las 15:24
-
Tengo curiosidad acerca de esto, si un miembro de la clase no tiene motivos para configurarse desde el exterior, ¿por qué deberíamos crear un setter solo con el propósito de probarlo? Imagine que la clase ‘Segunda’ aquí es en realidad un administrador o herramienta de FileSystem, inicializado durante la construcción del objeto a probar. Tengo todas las razones para querer burlarme de este administrador de FileSystem, para probar la primera clase, y ninguna razón para hacerlo accesible. Puedo hacer esto en Python, entonces, ¿por qué no con Mockito?
– Zangdar
21 de abril de 2020 a las 18:11
enero
Esto no es posible si no puede cambiar su 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 bonito y fácil.
-
Creo que esta es una mejor respuesta que las otras porque InjectMocks.
– sudocodificador
20/10/2015 a las 20:38
-
Es curioso cómo uno llega, como novato en pruebas como yo, a confiar en ciertas bibliotecas y marcos. Supuse que esto era solo una mala idea que indicaba la necesidad de rediseñar… hasta que me lo mostraste es de hecho posible (muy clara y limpiamente) en Mockito.
– mike roedor
29/10/2016 a las 19:17
-
Que es @Recurso?
– Igor Ganapolski
24 de abril de 2017 a las 19:26
-
@IgorGanapolsky the @ Resource es una anotación creada/utilizada por el marco Java Spring. Es una forma de indicarle a Spring que este es un bean/objeto administrado por Spring. stackoverflow.com/questions/4093504/resource-vs-autowired baeldung.com/spring-annotations-resource-inject-autowire No es una cosa simulada, pero debido a que se usa en la clase que no es de prueba, debe burlarse en la prueba.
– Grez.Kev
4 oct 2018 a las 13:10
-
No entiendo esta respuesta. ¿Dices que no es posible y luego muestras que es posible? ¿Qué es exactamente lo que no es posible aquí?
– JobHunter69
17 de junio de 2020 a las 19:59
control del alma
Si observa detenidamente su código, verá que el second
propiedad en su prueba sigue siendo una instancia de Second
no un simulacro (no le pasas el simulacro a first
en su código).
La forma más sencilla sería crear un setter para second
en First
class y pasarle el simulacro explícitamente.
Como 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());
}
Otra sería pasar un Second
instancia como First
Parámetro constructor de .
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 un código que no controlas (aunque uno puede imaginar un escenario en el que tiene que probar una biblioteca externa porque su autor no lo hizo :))
-
Entiendo. Probablemente seguiré tu primera sugerencia.
–Anand Hemmige
24 de enero de 2012 a las 23:14
-
Solo por curiosidad, ¿hay alguna forma o API que conozca que pueda simular un objeto/método a nivel de aplicación o de paquete? ? Supongo que lo que digo es que, en el ejemplo anterior, cuando me burlo del objeto ‘Segundo’, ¿hay alguna manera de que pueda anular cada instancia de Segundo que se usa durante el ciclo de vida de las pruebas? ?
–Anand Hemmige
24 de enero de 2012 a las 23:16
-
@AnandHemmige en realidad, el segundo (constructor) es más limpio, ya que evita crear instancias de ‘Segundas’ innecesarias. Tus clases se desacoplan muy bien de esa manera.
– examen de alma
24/01/2012 a las 23:30
-
Mockito proporciona algunas anotaciones agradables que le permiten inyectar sus simulacros en variables privadas. Anotar segundo con
@Mock
y anotar Primero con@InjectMocks
e instanciar First en el inicializador. Mockito automáticamente hará todo lo posible para encontrar un lugar para inyectar el segundo simulacro en la primera instancia, incluida la configuración de campos privados que coincidan con el tipo.– jhericks
25 de enero de 2012 a las 5:28
-
@Mock
estaba en 1.5 (tal vez antes, no estoy seguro). 1.8.3 introducido@InjectMocks
tanto como@Spy
y@Captor
.– jhericks
25 de enero de 2012 a las 16:53
Si no puede cambiar la variable miembro, 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 a new Second devolverá la misma instancia simulada. Pero en su caso simple esto funcionará.
Tuve el mismo problema en el que no se estableció un valor privado porque Mockito no llama a los superconstructores. Así es como aumento la burla con la reflexión.
Primero, creé una clase TestUtils que contiene muchas utilidades útiles, incluidos estos métodos de reflexión. El acceso a la reflexión es un poco difícil de implementar cada vez. Creé estos métodos para probar código en proyectos que, por una u otra razón, no tenían un paquete de simulación 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 de clase que no tiene 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 page. Podría haber un problema de compilación o dos. Creo que entiendes la idea general. Siéntete libre de tomar el código y usarlo si lo encuentras útil.
-
¿Podría explicar su código con un caso de uso real? como clase pública tobeMocker(){ classObject privado classObject; } Donde classObject es igual al objeto que se va a reemplazar.
–Jasper Lankhorst
23 de marzo de 2017 a las 11:09
-
En su ejemplo, si la instancia de ToBeMocker = new ToBeMocker(); y ClassObject someNewInstance = new ClassObject() { @Override //algo así como una dependencia externa }; luego TestUtils.refelctSetValue(instancia, “classObject”, someNewInstance); Tenga en cuenta que debe averiguar qué desea anular para burlarse. Digamos que tiene una base de datos y esta anulación devolverá un valor, por lo que no necesita seleccionar. Más recientemente, tuve un bus de servicio en el que no quería procesar el mensaje, pero quería asegurarme de que lo recibiera. Por lo tanto, configuré la instancia de bus privado de esta manera: ¿Te fue útil?
– dave
25 de marzo de 2017 a las 1:59
-
Tendrás que imaginar que hubo formato en ese comentario. Fue eliminado. Además, esto no funcionará con Java 9 ya que bloqueará el acceso privado. Tendremos que trabajar con otras construcciones una vez que tengamos un lanzamiento oficial y podamos trabajar dentro de sus límites reales.
– dave
25 de marzo de 2017 a las 2:00
-
Esto es realmente útil, a diferencia de otros comentarios que dicen: “esto no es posible” o que el código debe ser refactorizado y, lo que es peor, la propiedad/método privado debe hacerse público. Esta debería ser la respuesta aceptada, ya que resuelve el problema sin necesidad de refactorizar el código.
– AlexB
18 de noviembre de 2021 a las 22:49
Puede burlarse de las variables miembro de un Mockito Mock con ReflectionTestUtils
ReflectionTestUtils.setField(yourMock, "memberFieldName", value);
-
¿Podría explicar su código con un caso de uso real? como clase pública tobeMocker(){ classObject privado classObject; } Donde classObject es igual al objeto que se va a reemplazar.
–Jasper Lankhorst
23 de marzo de 2017 a las 11:09
-
En su ejemplo, si la instancia de ToBeMocker = new ToBeMocker(); y ClassObject someNewInstance = new ClassObject() { @Override //algo así como una dependencia externa }; luego TestUtils.refelctSetValue(instancia, “classObject”, someNewInstance); Tenga en cuenta que debe averiguar qué desea anular para burlarse. Digamos que tiene una base de datos y esta anulación devolverá un valor, por lo que no necesita seleccionar. Más recientemente, tuve un bus de servicio en el que no quería procesar el mensaje, pero quería asegurarme de que lo recibiera. Por lo tanto, configuré la instancia de bus privado de esta manera: ¿Te fue útil?
– dave
25 de marzo de 2017 a las 1:59
-
Tendrás que imaginar que hubo formato en ese comentario. Fue eliminado. Además, esto no funcionará con Java 9 ya que bloqueará el acceso privado. Tendremos que trabajar con otras construcciones una vez que tengamos un lanzamiento oficial y podamos trabajar dentro de sus límites reales.
– dave
25 de marzo de 2017 a las 2:00
-
Esto es realmente útil, a diferencia de otros comentarios que dicen: “esto no es posible” o que el código debe ser refactorizado y, lo que es peor, la propiedad/método privado debe hacerse público. Esta debería ser la respuesta aceptada, ya que resuelve el problema sin necesidad de refactorizar el código.
– AlexB
18 de noviembre de 2021 a las 22:49
Szymon Zwolinski
Si quieres una alternativa a ReflectionTestUtils de Spring en mockito, usa
Whitebox.setInternalState(first, "second", sec);
-
¡Bienvenido a Stack Overflow! Hay otras respuestas que brindan la pregunta del OP, y se publicaron hace muchos años. Al publicar una respuesta, asegúrese de agregar una nueva solución o una explicación sustancialmente mejor, especialmente al responder preguntas anteriores o comentar otras respuestas.
– ayuda-info.de
19 de abril de 2019 a las 13:14