java - run - solicitar frijoles con fondo en las pruebas de primavera
spring test example (8)
Me gustaría utilizar beans de ámbito de solicitud en mi aplicación. Yo uso JUnit4 para probar. Si intento crear uno en una prueba como esta:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:spring/TestScopedBeans-context.xml" })
public class TestScopedBeans {
protected final static Logger logger = Logger
.getLogger(TestScopedBeans.class);
@Resource
private Object tObj;
@Test
public void testBean() {
logger.debug(tObj);
}
@Test
public void testBean2() {
logger.debug(tObj);
}
Con la siguiente definición de frijol:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="java.lang.Object" id="tObj" scope="request" />
</beans>
Y entiendo:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name ''gov.nasa.arc.cx.sor.query.TestScopedBeans'': Injection of resource fields failed; nested exception is java.lang.IllegalStateException: No Scope registered for scope ''request''
<...SNIP...>
Caused by: java.lang.IllegalStateException: No Scope registered for scope ''request''
Así que encontré este blog que me pareció útil: http://www.javathinking.com/2009/06/no-scope-registered-for-scope-request_5.html
Pero noté que usa AbstractDependencyInjectionSpringContextTests que parece estar en desuso en Spring 3.0. Utilizo Spring 2.5 en este momento, pero pensé que no debería ser demasiado difícil cambiar este método para usar AbstractJUnit4SpringContextTests como lo sugieren los documentos (ok el enlace de documentos a la versión 3.8 pero estoy usando 4.4). Así que cambio la prueba para extender AbstractJUnit4SpringContextTests ... el mismo mensaje. El mismo problema. Y ahora el método prepareTestInstance () que deseo sobrescribir no está definido. OK, tal vez ponga esas llamadas registerScope en otro lugar ... Así que leo más sobre TestExecutionListeners y creo que sería mejor, ya que no quiero tener que heredar la estructura del paquete de primavera. Así que cambié mi prueba a:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:spring/TestScopedBeans-context.xml" })
@TestExecutionListeners({})
public class TestScopedBeans {
esperando tener que crear un oyente personalizado pero yo cuando lo ejecuté. ¡Funciona! Genial, pero ¿por qué? No veo dónde alguno de los oyentes stock registra el alcance de la solicitud o el alcance de la sesión, y ¿por qué lo harían? no hay nada que decir, quiero eso todavía, esto podría no ser un código Test for Spring MVC ...
Solución para Spring 3.2 o posterior
El resorte que comienza con la versión 3.2 proporciona soporte para beans de ámbito sesión / solicitud para pruebas de integración .
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfig.class)
@WebAppConfiguration
public class SampleTest {
@Autowired WebApplicationContext wac;
@Autowired MockHttpServletRequest request;
@Autowired MockHttpSession session;
@Autowired MySessionBean mySessionBean;
@Autowired MyRequestBean myRequestBean;
@Test
public void requestScope() throws Exception {
assertThat(myRequestBean)
.isSameAs(request.getAttribute("myRequestBean"));
assertThat(myRequestBean)
.isSameAs(wac.getBean("myRequestBean", MyRequestBean.class));
}
@Test
public void sessionScope() throws Exception {
assertThat(mySessionBean)
.isSameAs(session.getAttribute("mySessionBean"));
assertThat(mySessionBean)
.isSameAs(wac.getBean("mySessionBean", MySessionBean.class));
}
}
Leer más: Solicitud y habas de ámbito de sesión
Solución para Spring before 3.2 with listener
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfig.class)
@TestExecutionListeners({WebContextTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class})
public class SampleTest {
...
}
WebContextTestExecutionListener.java
public class WebContextTestExecutionListener extends AbstractTestExecutionListener {
@Override
public void prepareTestInstance(TestContext testContext) {
if (testContext.getApplicationContext() instanceof GenericApplicationContext) {
GenericApplicationContext context = (GenericApplicationContext) testContext.getApplicationContext();
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST,
new SimpleThreadScope());
beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION,
new SimpleThreadScope());
}
}
}
Solución para Spring before 3.2 con ámbitos personalizados
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfig.class, locations = "test-config.xml")
public class SampleTest {
...
}
TestConfig.java
@Configuration
@ComponentScan(...)
public class TestConfig {
@Bean
public CustomScopeConfigurer customScopeConfigurer(){
CustomScopeConfigurer scopeConfigurer = new CustomScopeConfigurer();
HashMap<String, Object> scopes = new HashMap<String, Object>();
scopes.put(WebApplicationContext.SCOPE_REQUEST,
new SimpleThreadScope());
scopes.put(WebApplicationContext.SCOPE_SESSION,
new SimpleThreadScope());
scopeConfigurer.setScopes(scopes);
return scopeConfigurer
}
o con configuración xml
test-config.xml
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="request">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
<map>
<entry key="session">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
</property>
</bean>
Código fuente
Código fuente para todas las soluciones presentadas:
Esto sigue siendo un problema abierto:
https://jira.springsource.org/browse/SPR-4588
Pude hacer que esto funcionara (principalmente) al definir un cargador de contexto personalizado como se describe en
He intentado varias soluciones, incluida la solución de @Marius con el "WebContextTestExecutionListener", pero no funcionó, ya que este código cargó el contexto de la aplicación antes de crear el alcance de la solicitud.
La respuesta que me ayudó al final no es nueva, pero es buena: http://tarunsapra.wordpress.com/2011/06/28/junit-spring-session-and-request-scope-beans/
Simplemente agregué el siguiente fragmento a mi contexto de aplicación (prueba):
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="request">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
</property>
</bean>
¡Buena suerte!
La prueba pasa porque no está haciendo nada :)
Cuando omite la anotación @TestExecutionListeners
, Spring registra 3 detectores predeterminados, incluido uno llamado DependencyInjectionTestExecutionListener
. Este es el oyente responsable de escanear tu clase de prueba en busca de cosas para inyectar, incluidas @Resource
anotaciones @Resource
. Este oyente intentó inyectar tObj
y falla debido al alcance indefinido.
Cuando declara @TestExecutionListeners({})
, suprime el registro de DependencyInjectionTestExecutionListener
, por lo que la prueba nunca obtiene inyectado en absoluto, y como su prueba no verifica la existencia de tObj
, se pasa.
Modifique su prueba para que lo haga y fallará:
@Test
public void testBean() {
assertNotNull("tObj is null", tObj);
}
Entonces con tus @TestExecutionListeners
vacíos, la prueba pasa porque no pasa nada .
Ahora, a tu problema original. Si quiere intentar registrar el alcance de la solicitud con su contexto de prueba, entonces eche un vistazo al código fuente de WebApplicationContextUtils.registerWebApplicationScopes()
, encontrará la línea:
beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
Podrías probar eso, y ver cómo te va, pero podría haber efectos secundarios extraños, porque en realidad no debes hacer esto en una prueba.
En su lugar, recomendaría reformular su prueba para que no necesite pedir frijoles de ámbito determinado. Esto no debería ser difícil, el ciclo de vida de @Test
no debería ser más largo que el ciclo de vida de un bean con ámbito de solicitud, si usted escribe pruebas independientes. Recuerde, no es necesario probar el mecanismo de determinación del alcance, es parte de Spring y puede suponer que funciona.
La solución de MariuszS funciona, excepto que no pude obtener la transacción comprometida correctamente.
Parece que el recién lanzado 3.2 finalmente ha hecho una petición de prueba / sesión de habichuelas de ciudadanos de primera clase. Aquí hay un par de blogs para más detalles.
Spring Framework 3.2 RC1 de Rossen Stoyanchev : Spring MVC Test Framework
Spring Framework 3.2 RC1 de Sam Brannen : nuevas características de prueba
NO leer los documentos a veces lo vuelve loco. Casi.
Si está utilizando beans de vida más corta (por ejemplo, alcance de solicitud), lo más probable es que también necesite cambiar su valor predeterminado de inicio lento. De lo contrario, el WebAppContext no se cargará y le dirá algo sobre el alcance de la solicitud faltante, que por supuesto no se encuentra, ¡porque el contexto aún se está cargando!
Los chicos de Spring definitivamente deberían poner esa sugerencia en su mensaje de excepción ...
Si no desea cambiar el valor predeterminado, también existe la forma de anotación: coloque "@Lazy (true)" después de @Component, etc. para hacer que los singletons se inicialicen como flojos y evite crear instancias de beans con ámbito de solicitud demasiado pronto.
Una solución, probada con Spring 4, para cuando necesita beans con alcance de solicitud pero no realiza ninguna solicitud a través de MockMVC
, etc.
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(/* ... */)
public class Tests {
@Autowired
private GenericApplicationContext context;
@Before
public void defineRequestScope() {
context.getBeanFactory().registerScope(
WebApplicationContext.SCOPE_REQUEST, new RequestScope());
RequestContextHolder.setRequestAttributes(
new ServletRequestAttributes(new MockHttpServletRequest()));
}
// ...
Solicitud de prueba: Scops Beans with Spring explica muy bien cómo registrarse y crear un alcance personalizado con Spring.
En pocas palabras, como Ido Cohn explicó, es suficiente agregar lo siguiente a la configuración del contexto de texto:
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="request">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
</property>
</bean>
En lugar de utilizar SimpleThreadScope predefinido, basado en ThreadLocal, también es fácil implementar uno personalizado, como se explica en el artículo.
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
public class CustomScope implements Scope {
private final Map<String , Object> beanMap = new HashMap<String , Object>();
public Object get(String name, ObjectFactory<?> factory) {
Object bean = beanMap.get(name);
if (null == bean) {
bean = factory.getObject();
beanMap.put(name, bean);
}
return bean;
}
public String getConversationId() {
// not needed
return null;
}
public void registerDestructionCallback(String arg0, Runnable arg1) {
// not needed
}
public Object remove(String obj) {
return beanMap.remove(obj);
}
public Object resolveContextualObject(String arg0) {
// not needed
return null;
}
}