Ich möchte in meiner App Anforderungsbereichs-Beans verwenden. Ich benutze JUnit4 zum Testen. Wenn ich versuche, in einem solchen Test einen zu erstellen:
@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);
}
Mit der folgenden Bean-Definition:
<?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>
Und ich bekomme:
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'
Also fand ich diesen Blog, der mir hilfreich erschien: http://www.javathinking.com/2009/06/no-scope-registriert-für-scope-request_5.html
Ich habe jedoch festgestellt, dass er AbstractDependencyInjectionSpringContextTests verwendet, das im Frühling 3.0 veraltet zu sein scheint. Ich verwende zu diesem Zeitpunkt Spring 2.5, aber ich dachte, es sollte nicht zu schwierig sein, diese Methode zu wechseln, um AbstractJUnit4SpringContextTests Zu verwenden. . Also ändere ich den Test, um AbstractJUnit4SpringContextTests ... dieselbe Nachricht zu erweitern. Gleiches Problem. Und jetzt ist die prepareTestInstance () - Methode, die ich Überschreiben möchte, nicht definiert. OK, vielleicht lege ich diese registerScope-Aufrufe woanders hin ... Also lese ich mehr über TestExecutionListeners und denke, das wäre besser, da ich die Federpaketstruktur nicht erben möchte. Also änderte ich meinen Test in:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:spring/TestScopedBeans-context.xml" })
@TestExecutionListeners({})
public class TestScopedBeans {
ich erwarte, dass ich einen benutzerdefinierten Listener erstellen muss, aber ich habe ihn ausgeführt. Es klappt! Großartig, aber warum? Ich sehe nicht, wo einer der Aktien-Listener Den Anforderungsbereich oder den Sitzungsbereich registriert, und warum? Es gibt nichts zu sagen, ich möchte das noch nicht, dies könnte kein Test für Spring-MVC-Code sein ...
Der Test ist bestanden, weil er nichts tut :)
Wenn Sie die @TestExecutionListeners
-Annotation weglassen, registriert Spring drei Standard-Listener, darunter einen mit dem Namen DependencyInjectionTestExecutionListener
. Dies ist der Listener, der für das Durchsuchen Ihrer Testklasse nach einzuführenden Elementen, einschließlich @Resource
-Anmerkungen, verantwortlich ist. Dieser Listener hat versucht, tObj
zu injizieren, und schlägt aufgrund des undefinierten Bereichs fehl.
Wenn Sie @TestExecutionListeners({})
deklarieren, unterdrücken Sie die Registrierung der DependencyInjectionTestExecutionListener
, und so wird dem Test niemals tObj
injiziert. Da Ihr Test nicht auf das Vorhandensein von tObj
prüft, wird er bestanden.
Ändern Sie Ihren Test so, dass er dies tut, und er wird fehlschlagen:
@Test
public void testBean() {
assertNotNull("tObj is null", tObj);
}
Mit Ihrem leeren @TestExecutionListeners
ist der Test deshalb bestanden, weil nichts passiert.
Nun zu Ihrem ursprünglichen Problem. Wenn Sie versuchen möchten, den Anforderungsbereich mit Ihrem Testkontext zu registrieren, sehen Sie sich den Quellcode für WebApplicationContextUtils.registerWebApplicationScopes()
an. Die Zeile lautet:
beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
Sie könnten es versuchen und sehen, wie es Ihnen geht, aber es könnte merkwürdige Nebenwirkungen geben, weil Sie dies nicht unbedingt in einem Test tun sollen.
Ich würde stattdessen empfehlen, Ihren Test neu zu formulieren, damit Sie keine need -Anforderungen von Bereichs-Beans erhalten. Dies sollte nicht schwierig sein, der Lebenszyklus von @Test
sollte nicht länger sein als der Lebenszyklus einer Bean für Anforderungsbereiche, wenn Sie in sich abgeschlossene Tests schreiben. Denken Sie daran, dass Sie den Scoping-Mechanismus nicht testen müssen. Er ist Teil von Spring und Sie können davon ausgehen, dass er funktioniert.
Spring ab Version 3.2 Bietet Unterstützung für Sitzungs Anforderungsbereichs-Beans für Integrationstests - /.
@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));
}
}
Lesen Sie mehr: Anfrage und Session Scoped Beans
@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());
}
}
}
@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
}
oder mit XML-Konfiguration
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>
Quellcode für alle präsentierten Lösungen:
Ich habe verschiedene Lösungen ausprobiert, einschließlich der Lösung von @ Marius mit dem "WebContextTestExecutionListener", aber es hat bei mir nicht funktioniert, da dieser Code den Anwendungskontext geladen hat, bevor der Anforderungsbereich erstellt wurde.
Die Antwort, die mir am Ende geholfen hat, ist keine neue, aber sie ist gut: http://tarunsapra.wordpress.com/2011/06/28/junit-spring-session-and-request-scope- Bohnen /
Ich habe einfach das folgende Snippet zu meinem (Test-) Anwendungskontext hinzugefügt:
<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>
Viel Glück!
Eine Lösung, die mit Spring 4 getestet wurde, wenn Beans mit Anforderungsbereich benötigt werden, jedoch keine Anforderungen über MockMVC
gestellt werden.
@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()));
}
// ...
Anforderungsbasierte Beans mit Spring testen erklärt sehr gut, wie man sich mit Spring registriert und einen benutzerdefinierten Bereich erstellt.
Kurz gesagt, wie Ido Cohn erklärt hat, reicht es aus, der Textkontextkonfiguration Folgendes hinzuzufügen:
<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>
Anstelle des vordefinierten SimpleThreadScope, basierend auf ThreadLocal, ist es auch einfach, ein benutzerdefiniertes zu implementieren, wie in dem Artikel beschrieben.
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;
}
}
Dies ist immer noch ein offenes Problem:
https://jira.springsource.org/browse/SPR-4588
Ich konnte dies (meistens) zum Laufen bringen, indem ich einen benutzerdefinierten Kontext-Loader wie in beschrieben definiere
Die Lösung von MariuszS funktioniert, es sei denn, die Transaktion konnte nicht ordnungsgemäß abgeschlossen werden.
Es sieht so aus, als ob das neu erschienene 3.2 endlich Testanfragen/Sitzungs-Beans erstklassiger Bürger gemacht hat. Hier sind ein paar Blogs für weitere Details.
Rossen Stoyanchevs Spring Framework 3.2 RC1: Spring MVC Test Framework
Sam Brannen's Spring Framework 3.2 RC1: Neue Testfunktionen
Das NICHT Lesen der Dokumente macht manchmal einen verrückt. Fast.
Wenn Sie kurzlebigere Beans verwenden (z. B. Anforderungsbereich), müssen Sie wahrscheinlich auch Ihren Lazy-Init-Standard ändern! Andernfalls wird der WebAppContext nicht geladen und sagt Ihnen etwas über den fehlenden Anforderungsumfang aus, der natürlich fehlt, da der Kontext noch geladen wird!
Die Spring-Jungs sollten diesen Hinweis unbedingt in ihre Ausnahmebotschaft aufnehmen ...
Wenn Sie den Standardwert nicht ändern möchten, gibt es auch die Annotationsmethode: Setzen Sie "@Lazy (true)" nach @Component usw., um Singletons zu initialisieren und Lazy zu initialisieren.