mediante - El servicio no se ha inyectado en la prueba del controlador Spring con spring-test-mvc
spring mvc 4 tutorial español (4)
Estoy intentando construir un conjunto de pruebas de unidad / integración para un servidor API Restful que he creado usando Spring MVC 3.1.1.
Estoy tratando de usar Spring-test-mvc .
Soy relativamente nuevo en Spring y por lo tanto en spring-test-mvc.
Incluiré secciones relevantes de mi código para darle una idea de cómo lo he estructurado:
PurchaseController:
@Controller
public class PurchaseController
{
@Autowired
private IPurchaseService purchaseService;
@RequestMapping(value = "purchases", method = RequestMethod.GET)
@ResponseBody
public final List<Purchase> getAll()
{
return purchaseService.getAll();
}
}
PurchaseService:
@Service
public class PurchaseService implements IPurchaseService
{
@Autowired
private IPurchaseDAO purchaseDAO;
public PurchaseService()
{
}
@Transactional
public List<Purchase> getAll()
{
return purchaseDAO.findAll();
}
}
CompraDAO:
@Repository
public class PurchaseDAO extends AbstractJpaDAO<Purchase> implements
IPurchaseDAO
{
@PersistenceContext
EntityManager entityManager;
public PurchaseDAO()
{
setClazz(Purchase.class);
}
}
ResumenJpaDAO:
public abstract class AbstractJpaDAO<T extends Serializable> implements
IAbstractJpaDAO<T> {
private Class<T> clazz;
@PersistenceContext
EntityManager entityManager;
public void setClazz(final Class<T> clazzToSet) {
this.clazz = clazzToSet;
}
public List<T> findAll() {
return entityManager.createQuery("from " + clazz.getName())
.getResultList();
}
}
Aquí está mi prueba de controlador:
public class PurchaseControllerTest extends AbstractControllerTest
{
@Autowired
private IPurchaseService purchaseService;
@Autowired
private PurchaseController purchaseController;
private MockMvc mockMvc;
@Before
public void setup()
{
// Test Purchase
Purchase purchase = new Purchase();
purchase.setPan(5412311111111121l);
purchase.setCvc((short) 122);
purchase.setExpiry("1215");
purchase.setMerchantName("TestMerchant");
purchase.setMerchantType("Airline");
purchase.setTransactionAmount(new BigDecimal("300.99"));
mockMvc = MockMvcBuilders.standaloneSetup(purchaseController).build();
purchaseService.addPurchase(purchase);
}
@Test
public void testGetAll() throws Exception
{
this.mockMvc
.perform(get("/purchase").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().type(MediaType.APPLICATION_JSON));
}
}
AbstractControllerTest:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {ControllerTestConfig.class}, loader=AnnotationConfigContextLoader.class)
@ActiveProfiles("test")
public class AbstractControllerTest
{
}
ControllerTestConfig:
@Configuration
@Profile("test")
public class ControllerTestConfig {
@Bean
public PurchaseService purchaseService()
{
return new PurchaseService();
}
@Bean
public PurchaseDAO purchaseDAO()
{
return new PurchaseDAO();
}
@Bean
public EntityManagerFactory entityManager()
{
return new LocalEntityManagerFactoryBean().getObject();
}
}
Obtengo el siguiente error al ejecutar mi prueba a través de JUnit de Eclipse:
2012-07-08 16:16:00,017 TRACE [main] o.s.c.s.GenericApplicationContext [AbstractApplicationContext.java:322] Publishing event in org.springframework.context.support.GenericApplicationContext@42b307f0: org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.support.GenericApplicationContext@42b307f0: startup date [Sun Jul 08 16:15:59 IST 2012]; root of context hierarchy]
2012-07-08 16:16:00,018 DEBUG [main] o.s.t.c.TestContext [TestContext.java:150] Storing ApplicationContext for test class [class com.app.controller.PurchaseControllerTest] in cache under key [[MergedContextConfiguration@2f8a49e0 testClass = PurchaseControllerTest, locations = ''{}'', classes = ''{class com.app.spring.testing.ControllerTestConfig}'', activeProfiles = ''{test}'', contextLoader = ''org.springframework.test.context.support.AnnotationConfigContextLoader'']].
2012-07-08 16:16:00,019 TRACE [main] o.s.b.CachedIntrospectionResults [CachedIntrospectionResults.java:222] Getting BeanInfo for class [com.app.controller.PurchaseControllerTest]
2012-07-08 16:16:00,022 TRACE [main] o.s.b.CachedIntrospectionResults [CachedIntrospectionResults.java:238] Caching PropertyDescriptors for class [com.app.controller.PurchaseControllerTest]
2012-07-08 16:16:00,022 TRACE [main] o.s.b.CachedIntrospectionResults [CachedIntrospectionResults.java:250] Found bean property ''class'' of type [java.lang.Class]
2012-07-08 16:16:00,023 TRACE [main] o.s.b.CachedIntrospectionResults [CachedIntrospectionResults.java:250] Found bean property ''mockMvc'' of type [org.springframework.test.web.server.MockMvc]
2012-07-08 16:16:00,026 DEBUG [main] o.s.b.f.a.InjectionMetadata [InjectionMetadata.java:60] Found injected element on class [com.app.controller.PurchaseControllerTest]: AutowiredFieldElement for private com.app.service.IPurchaseService com.app.controller.PurchaseControllerTest.purchaseService
2012-07-08 16:16:00,026 DEBUG [main] o.s.b.f.a.InjectionMetadata [InjectionMetadata.java:60] Found injected element on class [com.app.controller.PurchaseControllerTest]: AutowiredFieldElement for private com.app.controller.PurchaseController com.app.controller.PurchaseControllerTest.purchaseController
2012-07-08 16:16:00,027 DEBUG [main] o.s.b.f.a.InjectionMetadata [InjectionMetadata.java:85] Processing injected method of bean ''com.app.controller.PurchaseControllerTest'': AutowiredFieldElement for private com.app.service.IPurchaseService com.app.controller.PurchaseControllerTest.purchaseService
2012-07-08 16:16:00,034 ERROR [main] o.s.t.c.TestContextManager [TestContextManager.java:324] Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.DependencyInjectionTestExecutionListener@403ef810] to prepare test instance [com.app.controller.PurchaseControllerTest@656546ef]
org.springframework.beans.factory.BeanCreationException: Error creating bean with name ''com.app.controller.PurchaseControllerTest'': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.app.service.IPurchaseService com.app.controller.PurchaseControllerTest.purchaseService; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [com.app.service.IPurchaseService] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:287) ~[spring-beans-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1106) ~[spring-beans-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireBeanProperties(AbstractAutowireCapableBeanFactory.java:374) ~[spring-beans-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:110) ~[spring-test-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:75) ~[spring-test-3.1.1.RELEASE.jar:3.1.1.RELEASE]
¿Alguna idea sobre por qué mi PurchaseService no se crea ni se inyecta?
ACTUALIZAR
Así que agregué mi Bean de servicio de compra a ControllerTestConfig ... ahora recibo un error sobre mi purchaseDAO.
¿Cómo puedo agregar esto a mi ControllerTestConfig? ¿Tiene que estar anidado dentro del Bean de Compra?
2012-07-08 18:30:23,029 ERROR [main] o.s.t.c.TestContextManager [TestContextManager.java:324] Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.DependencyInjectionTestExecutionListener@56da6bf4] to prepare test instance [com.app.controller.PurchaseControllerTest@df4cbee]
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.TestContext.getApplicationContext(TestContext.java:157) ~[spring-test-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:109) ~[spring-test-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:75) ~[spring-test-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:321) ~[spring-test-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:211) [spring-test-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:288) [spring-test-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15) [junit-4.8.1.jar:na]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:290) [spring-test-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231) [spring-test-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) [junit-4.8.1.jar:na]
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193) [junit-4.8.1.jar:na]
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52) [junit-4.8.1.jar:na]
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191) [junit-4.8.1.jar:na]
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42) [junit-4.8.1.jar:na]
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184) [junit-4.8.1.jar:na]
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) [spring-test-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71) [spring-test-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.junit.runners.ParentRunner.run(ParentRunner.java:236) [junit-4.8.1.jar:na]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174) [spring-test-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) [.cp/:na]
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) [.cp/:na]
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) [.cp/:na]
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) [.cp/:na]
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) [.cp/:na]
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197) [.cp/:na]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name ''purchaseService'': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.app.persistence.dao.IPurchaseDAO com.app.service.impl.PurchaseService.purchaseDAO; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name ''purchaseDAO'': Injection of persistence dependencies failed; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [javax.persistence.EntityManagerFactory] is defined: expected single bean but found 0
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:287) ~[spring-beans-3.1.1.RELEASE.jar:3.1.1.RELEASE]
Agregué Bean PurchaseDAO a ControllerTestConfig.
Actualización 2
¡Así que ahora obtengo una excepción de puntero nulo después de agregar un EntityManagerFactory Bean a mi ControllerTestConfig!
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.TestContext.getApplicationContext(TestContext.java:157) ~[spring-test-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:109) ~[spring-test-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:75) ~[spring-test-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:321) ~[spring-test-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:211) [spring-test-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:288) [spring-test-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15) [junit-4.8.1.jar:na]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:290) [spring-test-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231) [spring-test-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) [junit-4.8.1.jar:na]
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193) [junit-4.8.1.jar:na]
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52) [junit-4.8.1.jar:na]
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191) [junit-4.8.1.jar:na]
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42) [junit-4.8.1.jar:na]
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184) [junit-4.8.1.jar:na]
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) [spring-test-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71) [spring-test-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.junit.runners.ParentRunner.run(ParentRunner.java:236) [junit-4.8.1.jar:na]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174) [spring-test-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) [.cp/:na]
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) [.cp/:na]
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) [.cp/:na]
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) [.cp/:na]
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) [.cp/:na]
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197) [.cp/:na]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name ''purchaseService'': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.app.persistence.dao.IPurchaseDAO com.app.service.impl.PurchaseService.purchaseDAO; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name ''purchaseDAO'': Injection of persistence dependencies failed; nested exception is java.lang.NullPointerException
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:287) ~[spring-beans-3.1.1.RELEASE.jar:3.1.1.RELEASE]
¿Algunas ideas? Gracias
Deberá incluir algunos @Bean
en su perfil @Configuration
test
.
Anteriormente no tenía ninguno, pero desde su actualización, veo que tiene su PurchaseService
agregado. Bueno, su PurchaseService
tiene un @Autowired PurchaseDao
que también debe agregarse a su perfil de test
.
Intente agregar esto a ControllerTestConfig
:
@Bean
public IPurchaseDao purchaseDao() {
return new PurchaseDao();
}
Su PurchaseDao
utiliza un EntityManager
que también puede ser necesario agregar a su clase ControllerTestConfig
. (No estoy muy familiarizado con esta parte, así que no puedo decir de manera definitiva ...)
Esto no funcionará ya que spring-test-mvc no elige la configuración de contexto desde ContextConfiguration de Junit, en su lugar puede hacerlo (suponiendo que sus beans de servicio se carguen en algún lugar mediante la configuración de ControllerTestConfig):
MockMvcBuilders.annotationConfigSetup(ControllerTestConfig.class).build().perform(get("/purchase").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().type(MediaType.APPLICATION_JSON));
Dado que está utilizando @ContextConfiguration (el marco TestContext) para cargar la configuración de Spring, supongo que quiere hacer una prueba de integración que implique cargar sus Controladores, Servicios y Repositorios desde su configuración de Spring. Sin embargo, está utilizando el método standaloneSetup de MockMvcBuilders, que está destinado a pruebas unitarias más enfocadas que se dirigen principalmente a controladores (los servicios y repositorios generalmente se burlan).
Considere usar el método webApplicationContextSetup de MockMvcBuilders. Aquí hay un ejemplo .
No sé si esto lo ayudará, pero para heredar el contexto de @ContextConfiguration
debe autoconectarse en la clase de prueba, y luego de eso, puede establecerlo como un contexto primario de su ControllerTestConfig.class
.
En mi configuración, separé la configuración de persistencia para que se cargue desde @ContextConfiguration
y la configuración de MVC se cargue desde annotationConfigSetup
.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader=AnnotationConfigContextLoader.class, classes={PersistenceJPAConfig.class})
public class PurchaseControllerTest {
@Autowired
private ApplicationContext ac;
private PurchaseService service;
private MockMvc mvc;
@Before
public void before() {
mvc = annotationConfigSetup(ControllerTestConfig.class).
setParentContext(ac).build();
service = ac.getBean(PurchaseService.class);
}
}
Como el annotationConfigSetup
no sabe que tiene una configuración externa, debe indicarlo directamente estableciendo su contexto principal.
No puede insertar su servicio dentro de su clase de prueba, pero puede recuperarlo del contexto y también hacerlo inyectar en sus controladores. Bueno, puede inyectar, pero no será el mismo servicio que será inyectado en sus controladores. Obteniéndolo del contexto que pasas como padre, garantizas que son iguales. Como me burlo de mis servicios, esto es importante.