Mockito: inyecta objetos reales en campos privados @Autowired

191

Estoy usando Mockito @Mocky @InjectMocksanotaciones para inyectar dependencias en campos privados que están anotados con Spring @Autowired:

@RunWith(MockitoJUnitRunner.class)
public class DemoTest {
    @Mock
    private SomeService service;

    @InjectMocks
    private Demo demo;

    /* ... */
}

y

public class Demo {

    @Autowired
    private SomeService service;

    /* ... */
}

Ahora me gustaría también inyectar objetos reales en @Autowiredcampos privados (sin setters). ¿Es esto posible o el mecanismo se limita a inyectar simulacros solamente?

usuario2286693
fuente
55
Normalmente cuando te estás burlando de las cosas, implica que no te importa mucho el objeto concreto; que solo te importa realmente el comportamiento del objeto burlado. ¿Quizás quieras hacer una prueba de integración? O, ¿podría proporcionar una justificación de por qué quiere que se burlen de objetos concretos y que vivan juntos?
Makoto
2
Bueno, estoy lidiando con el código heredado y tomaría mucho tiempo cuando (...). Luego Declaraciones de devolución (...) para configurar el simulacro solo para evitar algunos NPE y similares. Por otro lado, un objeto real podría usarse de manera segura para esto. Por lo tanto, sería muy útil tener una opción para inyectar objetos reales junto con simulacros. Incluso si esto puede ser un olor a código, lo considero razonable en este caso particular.
usuario2286693
No olvides MockitoAnnotations.initMocks(this);el @Beforemétodo. Sé que no está directamente relacionado con la pregunta original, pero para cualquiera que venga más tarde, sería necesario agregarlo para que esto sea ejecutable.
Cuga
77
@ Cuga: si usas el corredor Mockito para JUnit, ( @RunWith(MockitoJUnitRunner.class)), no necesitas la líneaMockitoAnnotations.initMocks(this);
Clint Eastwood
1
Gracias. Nunca supe eso y siempre he estado especificando ambas
cosas

Respuestas:

306

Usa @Spyanotaciones

@RunWith(MockitoJUnitRunner.class)
public class DemoTest {
    @Spy
    private SomeService service = new RealServiceImpl();

    @InjectMocks
    private Demo demo;

    /* ... */
}

Mockito considerará todos los campos que tengan @Mocko @Spyanotación como candidatos potenciales para ser inyectados en la instancia anotada con @InjectMocksanotación. En el caso anterior, la 'RealServiceImpl'instancia se inyectará en la 'demostración'

Para más detalles consulte

Mockito-home

@Espiar

@Burlarse de

Dev Blanked
fuente
9
+1: funcionó para mí ... excepto para los objetos de cadena. Mockito se queja:Mockito cannot mock/spy following: - final classes - anonymous classes - primitive types
Adrian Pronk
Gracias También funcionó para mí :) Para que el proxy use simulacros de otra manera para una implementación real, use espía
Swarit Agarwal
¡Gracias! Eso es exactamente lo que necesitaba!
nterry
2
En mi caso, Mockito no está inyectando un espía. Sin embargo, inyecta un simulacro. El campo es privado y sin setter.
Vituel
8
Por cierto, no es necesario new RealServiceImpl(), @Spy private SomeService service;crea un objeto real utilizando el constructor predeterminado antes de espiarlo de todos modos.
Parxier
20

Además de la respuesta @Dev Blanked, si desea utilizar un bean existente creado por Spring, el código puede modificarse para:

@RunWith(MockitoJUnitRunner.class)
public class DemoTest {

    @Inject
    private ApplicationContext ctx;

    @Spy
    private SomeService service;

    @InjectMocks
    private Demo demo;

    @Before
    public void setUp(){
        service = ctx.getBean(SomeService.class);
    }

    /* ... */
}

De esta manera, no necesita cambiar su código (agregar otro constructor) solo para que las pruebas funcionen.

Yoaz Menda
fuente
2
@Aada ¿Puedes elaborar?
Yoaz Menda
1
De qué biblioteca proviene esto, solo puedo ver InjectMocks en org.mockito
Sameer
1
@sameer import org.mockito.InjectMocks;
Yoaz Menda
Agregar un comentario para cancelar Aada's. Esto funcionó para mí. No podía "simplemente no usar la inyección de campo" como lo sugieren otras respuestas, así que tuve que asegurarme de que los Autowiredcampos de las dependencias se crearon correctamente.
Scrambo
1
He intentado esto pero recibo una excepción de puntero nulo del método de configuración
Akhil Surapuram
3

Mockito no es un marco DI e incluso los marcos DI alientan las inyecciones de constructor sobre las inyecciones de campo.
Entonces solo declaras un constructor para establecer dependencias de la clase bajo prueba:

@Mock
private SomeService serviceMock;

private Demo demo;

/* ... */
@BeforeEach
public void beforeEach(){
   demo = new Demo(serviceMock);
}

Usar Mockito spypara el caso general es un consejo terrible. Hace que la clase de prueba sea frágil, no recta y propensa a errores: ¿qué se burla realmente? ¿Qué se prueba realmente?
@InjectMocksy @Spytambién perjudica el diseño general, ya que fomenta las clases hinchadas y las responsabilidades mixtas en las clases.
Por favor leer el spy()javadoc antes de usar que ciegamente (el subrayado no es mío):

Crea un espía del objeto real. El espía llama métodos reales a menos que estén tropezados. Los espías reales deben usarse con cuidado y ocasionalmente , por ejemplo, cuando se trata de código heredado.

Como de costumbre, leerá partial mock warning: La programación orientada a objetos aborda la complejidad dividiendo la complejidad en objetos SRPy específicos y separados. ¿Cómo encaja la simulación parcial en este paradigma? Bueno, simplemente no ... El simulacro parcial generalmente significa que la complejidad se ha movido a un método diferente en el mismo objeto. En la mayoría de los casos, esta no es la forma en que desea diseñar su aplicación.

Sin embargo, hay casos raros en que los simulacros parciales son útiles: lidiar con código que no puede cambiar fácilmente (interfaces de terceros, refactorización provisional de código heredado, etc.) Sin embargo, no usaría simulacros parciales para nuevos, basados ​​en pruebas y bien- código diseñado

davidxxx
fuente
0

En Spring hay una utilidad dedicada llamada ReflectionTestUtilspara este propósito. Tome la instancia específica e inyecte en el campo.


@Spy
..
@Mock
..

@InjectMock
Foo foo;

@BeforeEach
void _before(){
   ReflectionTestUtils.setField(foo,"bar", new BarImpl());// `bar` is private field
}
takacsot
fuente
-1

Sé que esta es una vieja pregunta, pero nos encontramos con el mismo problema al intentar inyectar cadenas. Entonces inventamos una extensión JUnit5 / Mockito que hace exactamente lo que quieres: https://github.com/exabrial/mockito-object-injection

EDITAR:

@InjectionMap
 private Map<String, Object> injectionMap = new HashMap<>();

 @BeforeEach
 public void beforeEach() throws Exception {
  injectionMap.put("securityEnabled", Boolean.TRUE);
 }

 @AfterEach
 public void afterEach() throws Exception {
  injectionMap.clear();
 }
Jonathan S. Fisher
fuente