【问题标题】:request scoped beans in spring testing在春季测试中请求范围内的bean
【发布时间】:2011-01-25 13:30:57
【问题描述】:

我想在我的应用程序中使用请求范围的 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() 方法 未定义要覆盖。好的,也许我会将这些 registerScope 调用放在其他地方......所以我阅读了更多关于TestExecutionListeners 的内容,并认为这会更好,因为我不想继承 spring 包结构。所以 我将我的测试更改为:

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

希望我必须创建一个自定义侦听器,但当我运行它时。有用!很好,但为什么呢?我看不到任何股票听众在哪里 正在注册请求范围或会话范围,为什么要注册?没什么可说的,我想要那个,这可能不是 Spring MVC 代码的测试......

【问题讨论】:

标签: java spring junit spring-mvc spring-test


【解决方案1】:

Spring 3.2 或更高版本的解决方案

Spring 从 3.2 版开始provides support for session/request scoped beans for integration testing

@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


Spring 3.2 之前的监听器解决方案

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

源代码

所有提供的解决方案的源代码:

【讨论】:

  • 嗯,它在第一行 testContext.getApplicationContext() 失败,错误消息 No Scope registered for scope 'request',因为它从 XML 和 @Configuration 读取上下文,并且一些 bean 在那里定义了“请求”范围。例如我有:@Configuration class MyConf { @Bean @Scope("request") provideFoo() {return new Foo()}}
  • 带有监听器的解决方案对我来说效果很好,经过测试,完整代码在这里:github.com/mariuszs/spring-test-web/blob/master/src/test/java/…
  • 因为您的 @Configuration 没有定义任何 bean。尝试在@Configuration 中添加一个方法,例如@Bean @Scope("request") @Autowired public String provideFoo(SomeBean dependency) {return dependency.toString()}。它失败了,因为 SomeBean 尚未创建。
  • xml 需要两个条目的映射,我看到的是两个映射?
【解决方案2】:

我尝试了几种解决方案,包括 @Marius 的“WebContextTestExecutionListener”解决方案,但它对我不起作用,因为此代码在创建请求范围之前加载了应用程序上下文。

最后帮助我的答案不是新的,但很好: http://tarunsapra.wordpress.com/2011/06/28/junit-spring-session-and-request-scope-beans/

我只是将以下 sn-p 添加到我的(测试)应用程序上下文中:

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

祝你好运!

【讨论】:

  • @WebAppConfiguration 的“问题”似乎是它不会为您注册真正的请求范围。如果您需要一个范围代理,请使用上面的答案手动注册范围。
【解决方案3】:

测试通过了,因为它什么也没做 :)

当您省略 @TestExecutionListeners 注释时,Spring 注册 3 个默认侦听器,包括一个名为 DependencyInjectionTestExecutionListener 的侦听器。这是负责扫描您的测试类以查找要注入的内容的侦听器,包括@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。这应该不难,@Test 的生命周期不应长于请求范围 bean 的生命周期,如果您编写自包含测试。请记住,无需测试作用域机制,它是 Spring 的一部分,您可以假设它有效。

【讨论】:

  • 啊,是的,谢谢。我不关心测试是否通过,因为我只想创建 bean,当我编写测试时,我认为我不会在某个时候关闭注入:-)。至于改写测试……不。重点是看看我如何让请求范围的 bean 在 JUnit 或 Web 应用程序中工作。
  • 哦,当我说作品时,我的意思是“跑步”。再次,我只是在寻找异常消失,认为这意味着我现在有一个请求范围的 bean。
  • 谢谢,但是您将如何“改写”您的测试以不需要请求范围 bean?假设我正在测试具有 @Autowired Provider fooDao 的 FooController。我不是在嘲笑 fooDao,因为这是集成测试,而不是单元(单元测试根本不需要 Spring 上下文),我真的需要真正的 FooDao。你如何注入请求范围的 fooDao?
  • 另外,要获得一个beanFactory,你需要一些Context。并且在阅读该上下文时失败:No Scope registered for scope 'request'。另请参阅我对 MariuszS 答案的评论。
  • 这句话让我很开心——我一直在努力让 junit 理解请求范围。阅读完这篇文章后,我刚刚将我的测试 bean 配置为原型范围 - 并且宾果游戏所有错误都消失了。我为此头疼了一天多 - “相反,我建议重新措辞你的测试,这样你就不需要请求范围的 bean。这应该不难,@Test 的生命周期不应该再如果您编写自包含测试,则比请求范围 bean 的生命周期更重要。请记住,无需测试范围机制,它是 Spring 的一部分,您可以假设它可以工作。”
【解决方案4】:

使用 Spring 4 测试的解决方案,用于当您需要请求范围的 bean 但未通过 MockMVC 等发出任何请求时。

@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】:

    这仍然是一个悬而未决的问题:

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

    我能够(主要)通过定义一个自定义上下文加载器(如

    中所述)来使其工作

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

    【讨论】:

      【解决方案6】:

      Test Request-Scoped Beans with Spring 很好地解释了如何使用 Spring 注册和创建自定义范围。

      简而言之,正如 Ido Cohn 所解释的,将以下内容添加到文本上下文配置中就足够了:

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

      【讨论】:

        【解决方案7】:

        MariuszS 的解决方案有效,但我无法正确提交事务。

        似乎新发布的 3.2 终于使测试请求/会话范围的 bean 成为一等公民。这里有几个博客了解更多详细信息。

        罗森·斯托扬切夫的Spring Framework 3.2 RC1: Spring MVC Test Framework

        Sam Brannen 的Spring Framework 3.2 RC1: New Testing Features

        【讨论】:

          【解决方案8】:

          不阅读文档有时会让人发疯。差不多了。

          如果您使用的是寿命较短的 bean(例如请求范围),您很可能还需要更改惰性初始化默认值!否则 WebAppContext 将无法加载并告诉您有关缺少请求范围的信息,这当然是缺少的,因为上下文仍在加载!

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

          Spring 家伙绝对应该将这个提示放入他们的异常消息中......

          如果不想改变默认值,还有注解的方式:在@Component等后面加上“@Lazy(true)”,使单例初始化惰性,避免过早实例化request-scoped beans。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-03-06
            • 1970-01-01
            • 2017-03-18
            • 1970-01-01
            • 2020-12-02
            相关资源
            最近更新 更多