web-dev-qa-db-ja.com

春のテストでスコープBeanをリクエストする

アプリでリクエストスコープBeanを利用したいのですが。テストにはJUnit4を使用しています。次のようなテストで作成しようとすると:

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

次のBean定義の場合:

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

そして私は得る:

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'

だから私はこのブログが役に立ったと思った: http://www.javathinking.com/2009/06/no-scope-registered-for-scope-request_5.html

しかし、私は彼が AbstractDependencyInjectionSpringContextTests を使用していることに気づきました。これは、Spring 3.0で廃止されたようです。私は現時点でSpring 2.5を使用していますが、ドキュメントが示唆するように、このメソッドをAbstractJUnit4SpringContextTestsに切り替えるのはそれほど難しいことではないと考えました(ドキュメントのリンクは3.8バージョンですが、4.4を使用しています)。テストを変更してAbstractJUnit4SpringContextTestsを拡張します...同じメッセージです。同じ問題。そして今、オーバーライドしたいprepareTestInstance()メソッドが定義されていません。 OK、多分私はそれらのregisterScope呼び出しを別の場所に置きます...それで TestExecutionListeners についてもっと読んで、春のパッケージ構造を継承する必要がないのでそれがより良いと思います。だから私は私のテストを次のように変更しました:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:spring/TestScopedBeans-context.xml" })
@TestExecutionListeners({})
public class TestScopedBeans {

カスタムリスナーを作成する必要があると思っていましたが、実行したときです。できます!素晴らしいですが、なぜですか?ストックリスナーのどこがリクエストスコープまたはセッションスコープを登録しているのかわかりません。なぜですか。まだ言いたいことは何もありませんが、これはTest for Spring MVCコードではないかもしれません...

29
harschware

それは何もしていないのでテストは合格です:)

_@TestExecutionListeners_アノテーションを省略すると、SpringはDependencyInjectionTestExecutionListenerと呼ばれるリスナーを含む3つのデフォルトリスナーを登録します。これは、_@Resource_アノテーションなど、注入するものを探すためにテストクラスをスキャンするリスナーです。このリスナーはtObjを挿入しようとしましたが、スコープが定義されていないため失敗します。

@TestExecutionListeners({})を宣言すると、DependencyInjectionTestExecutionListenerの登録を抑制します。そのため、テストはtObjをまったく注入しません。これは、テストが存在を確認していないためです。 tObjの場合、合格です。

これを行うようにテストを変更すると、失敗します。

_@Test
public void testBean() {
    assertNotNull("tObj is null", tObj);
}
_

したがって、空の_@TestExecutionListeners_を使用すると、何も起こらないため、テストは成功します。

では、元の問題に移りましょう。リクエストスコープをテストコンテキストに登録したい場合は、WebApplicationContextUtils.registerWebApplicationScopes()のソースコードを確認すると、次の行が見つかります。

_beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
_

これを試してみて、どのように進むかを確認することもできますが、実際にはテストでこれを行うことを意図していないため、奇妙な副作用が発生する可能性があります。

代わりに、スコープ付きBeanを要求するneedしないように、テストを言い換えることをお勧めします。自己完結型のテストを作成する場合、これは難しいことではありません。_@Test_のライフサイクルは、リクエストスコープのBeanのライフサイクルより長くなることはありません。スコーピングメカニズムをテストする必要はありません。これはSpringの一部であり、正常に機能すると想定できます。

8
skaffman

Spring 3.2以降のソリューション

Springバージョン3.2以降 統合テスト用のセッション/リクエストスコープBeanのサポートを提供します

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

続きを読む: Request and Session Scoped Beans


リスナー付き3.2より前のSpringのソリューション

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

カスタムスコープを使用した3.2より前のSpringのソリューション

@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

}

または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>

ソースコード

提示されたすべてのソリューションのソースコード:

53
MariuszS

「WebContextTestExecutionListener」を使用した@Mariusのソリューションを含むいくつかのソリューションを試しましたが、このコードはリクエストスコープを作成する前にアプリケーションコンテキストをロードしたため、機能しませんでした。

最後に私を助けた答えは新しいものではありませんが、それは良いことです: http://tarunsapra.wordpress.com/2011/06/28/junit-spring-session-and-request-scope-豆/

(テスト)アプリケーションコンテキストに次のスニペットを追加しただけです。

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

幸運を!

9
Ido Cohn

要求スコープのBeanが必要であるがMockMVCなどを介して要求を行わない場合の、Spring 4でテストされたソリューション。

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

    // ...
5
OrangeDog

SpringでリクエストスコープBeanをテストする は、Springでカスタムスコープを登録および作成する方法を非常によく説明しています。

一言で言えば、イドコーンが説明したように、テキストコンテキスト構成に以下を追加するだけで十分です。

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

記事で説明されているように、ThreadLocalに基づいて事前定義されたSimpleThreadScopeを使用する代わりに、カスタムスコープを実装することも簡単です。

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

これはまだ未解決の問題です:

https://jira.springsource.org/browse/SPR-4588

で概説されているようにカスタムコンテキストローダーを定義することでこれを(ほとんど)動作させることができました

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

2
Jim Cox

MariuszSのソリューションは機能しますが、トランザクションを適切にコミットできませんでした。

新しくリリースされた3.2がようやくテスト要求/セッションスコープBeanファーストクラスの市民を作ったようです。詳細については、いくつかのブログをご覧ください。

Rossen Stoyanchevの Spring Framework 3.2 RC1:Spring MVC Test Framework

Sam Brannenの Spring Framework 3.2 RC1:新しいテスト機能

1

ドキュメントを読まないと、時々気が狂います。ほとんど。

存続期間の短いBean(リクエストスコープなど)を使用している場合は、遅延initのデフォルトを変更する必要もあるでしょう。そうでない場合、WebAppContextはロードに失敗し、コンテキストがまだロードされているため、欠落しているリクエストスコープに関する情報を提供します。

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

春の連中は間違いなくそのヒントを例外メッセージに入れるべきだ...

デフォルトを変更したくない場合は、アノテーションの方法もあります。@ Componentなどの後に「@Lazy(true)」を配置して、シングルトンでレイジーを初期化し、リクエストスコープのBeanが早くインスタンス化されないようにします。

0
user1050755