在春季测试中请求范围豆

我想在我的应用程序中使用请求范围的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定义:

      

我得到:

 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'  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的内容,并认为这样会更好,因为我不想inheritancespring包结构。 所以我把我的测试改为:

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

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

测试通过,因为它没有做任何事情:)

当省略@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的一部分,你可以认为它有效。

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

从版本3.2开始的Spring 为集成测试提供了对会话/请求范围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)); } } 

阅读更多: 请求和会话范围豆


带有监听器的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 scopes = new HashMap(); scopes.put(WebApplicationContext.SCOPE_REQUEST, new SimpleThreadScope()); scopes.put(WebApplicationContext.SCOPE_SESSION, new SimpleThreadScope()); scopeConfigurer.setScopes(scopes); return scopeConfigurer } 

或者使用xml配置

test-config.xml

               

源代码

所有呈现解决方案的源代码:

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

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

我只是将以下代码段添加到我的(测试)应用程序上下文中:

          

祝你好运!

一个使用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())); } // ... 

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

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

通过定义如下所述的自定义上下文加载器,我能够(大多数情况下)使用它

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

使用Spring的测试请求 – Scoped Bean非常好地解释了如何使用Spring注册和创建自定义范围。

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

          

而不是使用基于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 beanMap = new HashMap(); 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; } } 

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

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

Rossen Stoyanchev的Spring Framework 3.2 RC1:Spring MVC测试框架

Sam Brannen的Spring Framework 3.2 RC1:新的测试function

不阅读文档有时会让人发疯。 几乎。

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

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

spring的家伙们一定要把这个暗示放到他们的exception信息中……

如果您不想更改默认值,还有注释方式:在@Component等之后放置“@Lazy(true)”以使单例初始化延迟并避免过早地实例化请求范围的bean。