java - sirve - ¿Cuál es la mejor manera de inyectar dependencias de Spring @Autowired simuladas desde una prueba de unidad?
pruebas unitarias spring boot (3)
2) Use la inyección del constructor @Autowired (si esa es la opción 2; de lo contrario, haga una opción 5)
Los constructores adecuados que crean un objeto en un estado válido es un enfoque orientado más correctamente al objeto, y perder los proxies cglib es relativamente poco importante ya que todos codificamos a las interfaces de todos modos, ¿verdad?
import org.springframework.beans.factory.annotation.Autowired;
class MyService {
@Autowired private DependencyOne dependencyOne;
@Autowired private DependencyTwo dependencyTwo;
public void doSomething(){
//Does something with dependencies
}
}
Al probar esta clase, básicamente tengo cuatro formas de inyectar dependencias simuladas:
- Use las Pruebas de Reflexión de Spring en la prueba para inyectar las dependencias
- Añadir un constructor a MyService
- Añadir métodos de establecimiento a MyService
- Relaje la visibilidad de la dependencia a paquetes protegidos y configure los campos directamente
¿Cuál es el mejor y por qué?
--- ACTUALIZACIÓN ---
Creo que debería haber sido un poco más claro: solo estoy hablando de las pruebas de estilo de "unidad", no de las pruebas de estilo de "integración" de Spring, donde las dependencias pueden conectarse mediante el uso de un contexto de Spring.
ContextConfiguration de Spring puede hacer esto por ti.
Por ejemplo, en el contexto de prueba debajo de las clases "locales" son simulacros. NotificationService
es la clase que quiero probar.
Estoy utilizando el escaneo de componentes para llevar las simulaciones al contexto, pero puede usar las declaraciones <bean>
misma facilidad. Tenga en cuenta el uso de use-default-filters = "false".
<context:component-scan base-package="com.foo.config" use-default-filters="false">
<context:include-filter type="assignable"
expression="com.foo.LocalNotificationConfig"/>
</context:component-scan>
<context:component-scan base-package="com.foo.services.notification"
use-default-filters="false">
<context:include-filter type="assignable"
expression="com.foo.services.notification.DelegatingTemplateService"/>
<context:include-filter type="assignable"
expression="com.foo.services.notification.NotificationService"/>
</context:component-scan>
<context:component-scan base-package="com.foo.domain"/>
DelegatingTemplateService es una clase Groovy con un @Delegate.
class DelegatingTemplateService {
@Delegate
TemplateService delegate
}
En la clase de prueba, uso el contexto de prueba e inyecto el servicio para probar. En la configuración configuré el delegado del DelegatingTemplateService:
@RunWith(classOf[SpringJUnit4ClassRunner])
@ContextConfiguration(Array("/spring-test-context.xml"))
class TestNotificationService extends JUnitSuite {
@Autowired var notificationService: NotificationService = _
@Autowired var templateService: DelegatingTemplateService = _
@Before
def setUp {
templateService.delegate = /* Your dynamic mock here */
}
En el servicio los campos @Autowired son privados:
@Component("notificationService")
class NotificationServiceImpl extends NotificationService {
@Autowired private var domainManager: DomainManager = _
@Autowired private var templateService: TemplateService = _
@Autowired private var notificationConfig: NotificationConfig = _
Use ReflectionTestUtils
o ponga un setter. Cualquiera esta bien. Agregar constructores puede tener efectos secundarios (no permitir subclases por parte de CGLIB por ejemplo), y relajar la visibilidad por el simple hecho de realizar pruebas no es un buen enfoque.