webentwicklung-frage-antwort-db.com.de

fordern Sie im Frühlingsversuch Scoped-Beans an

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 ...

26
harschware

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. 

8
skaffman

Lösung für Spring 3.2 oder neuer

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


Lösung für den Frühling vor 3.2 mit Zuhörer

@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());
        }
    }
}

Lösung für den Frühling vor 3.2 mit benutzerdefinierten Bereichen

@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

Quellcode für alle präsentierten Lösungen:

50
MariuszS

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!

9
Ido Cohn

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()));
    }

    // ...
4
OrangeDog

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;
    }
}
2
stivlo

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 

http://forum.springsource.org/showthread.php?p=286280

2
Jim Cox

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

1

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!

http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/beans.html#beans-factory-lazy-init

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.

0
user1050755