英文:
Request-scoped beans not working in Spring tests with Cucumber
问题
I have an application based on Spring 4.3.28 (i.e. not Spring Boot!) and I want to migrate my integration tests to Cucumber.
我有一个基于Spring 4.3.28的应用程序(不是Spring Boot!),我想迁移我的集成测试到Cucumber。
I’ve followed this tutorial and adapted it to plain Spring.
我已经按照这个教程并将其适应为普通的Spring。
The tests I’ve written so far are working fine (Spring context is initialized etc.), but as soon as there are request-scoped beans involved, they stop working:
到目前为止,我编写的测试都运行正常(Spring上下文已初始化等),但一旦涉及到请求范围的bean,它们就停止工作:
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you
referring to request attributes outside of an actual web request, or processing a
request outside of the originally receiving thread? If you are actually operating
within a web request and still receive this message, your code is probably running
outside of DispatcherServlet/DispatcherPortlet: In this case, use
RequestContextListener or RequestContextFilter to expose the current request.
导致的错误是:java.lang.IllegalStateException: 没有找到与线程关联的请求:您是否在实际Web请求之外引用了请求属性,或者在最初接收请求的线程之外处理请求?如果您实际上是在Web请求内操作,并且仍然收到此消息,则您的代码可能在DispatcherServlet/DispatcherPortlet之外运行:在这种情况下,请使用RequestContextListener或RequestContextFilter来公开当前请求。
I’ve created a small sample project that tries to reproduce the problem.
我创建了一个小示例项目,试图重现这个问题。
There is one context configuration class called AppConfig:
有一个名为AppConfig的上下文配置类:
@Configuration
public class AppConfig {
@Bean
@Scope("request") // when this line is removed, the test succeeds
public ExampleService exampleService() {
return new ExampleService();
}
@Bean("dependency")
@Scope("request") // when this line is removed, the test succeeds
public String dependencyBean() {
return "dependency bean";
}
}
ExampleService is request-scoped and gets one request-scoped bean injected by @Autowired:
ExampleService是请求范围的,并通过@Autowired注入了一个请求范围的bean:
public class ExampleService {
@Autowired
@Qualifier("dependency")
String dependencyBean;
public String process() { return "I have a "+dependencyBean; }
}
For the tests, I have one Spring-annotated superclass:
对于测试,我有一个使用Spring注解的超类:
@ContextConfiguration(classes = AppConfig.class)
@CucumberContextConfiguration
@WebAppConfiguration
public class TestBase {
@Autowired
public ExampleService underTest;
}
There’s also a plain Spring test that runs just fine:
还有一个普通的Spring测试,运行正常:
@RunWith(SpringRunner.class)
public class ExampleServicePlainSpringTest extends TestBase {
@Test
public void whenProcessingDataThenResultShouldBeReturned() {
assertThat(this.underTest.process()).isEqualTo("I have a dependency bean");
}
}
The Cucumber test is executed by this test class stub:
Cucumber测试由这个测试类桩运行:
@RunWith(Cucumber.class)
public class ExampleServiceCucumberTest extends TestBase {}
The actual cucumber step definitions are here:
实际的Cucumber步骤定义在这里:
public class CucumberStepDefinitions extends TestBase {
private String result;
@When("I process data")
public void iProcessData() {
result = this.underTest.process();
}
@Then("the result should be returned")
public void checkResult() {
assertThat(result).isEqualTo("I have a dependency bean");
}
}
The .feature file for Cucumber is in the src/test/resources directory under the same package name as the step definitions class:
Cucumber的.feature文件位于src/test/resources目录下,与步骤定义类的包名相同:
Feature: Example
Scenario: Example service bean returns dependency
When I process data
Then the result should be returned
Usually when I encountered the "no thread-bound request found" error, it was because the @WebAppConfiguration
annotation was missing, or when I tried to inject a request-scoped bean into a non-request scoped bean. But that’s not the case here. What am I doing wrong?
通常,当我遇到“没有找到与线程关联的请求”错误时,这是因为缺少@WebAppConfiguration
注释,或者我尝试将一个请求范围的bean注入到非请求范围的bean中。但在这里并非如此。我做错了什么?
英文:
I have an application based on Spring 4.3.28 (i.e. not Spring Boot!) and I want to migrate my integration tests to Cucumber.
I’ve followed this tutorial and adapted it to plain Spring.
The tests I’ve written so far are working fine (Spring context is initialized etc.), but as soon as there are request-scoped beans involved, they stop working:
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you
referring to request attributes outside of an actual web request, or processing a
request outside of the originally receiving thread? If you are actually operating
within a web request and still receive this message, your code is probably running
outside of DispatcherServlet/DispatcherPortlet: In this case, use
RequestContextListener or RequestContextFilter to expose the current request.
I’ve created a small sample project
that tries to reproduce the problem.
There is one context configuration class called AppConfig:
@Configuration
public class AppConfig {
@Bean
@Scope("request“) // when this line is removed, the test succeeds
public ExampleService exampleService() {
return new ExampleService();
}
@Bean("dependency")
@Scope("request") // when this line is removed, the test succeeds
public String dependencyBean() {
return "dependency bean";
}
}
The ExampleService is request-scoped, and gets one request-scoped bean injected by @Autowired:
public class ExampleService {
@Autowired
@Qualifier("dependency")
String dependencyBean;
public String process() { return "I have a "+dependencyBean; }
}
For the tests, I have one Spring-annotated superclass:
@ContextConfiguration(classes = AppConfig.class)
@CucumberContextConfiguration
@WebAppConfiguration
public class TestBase {
@Autowired
public ExampleService underTest;
}
There’s also a plain Spring test that runs just fine:
@RunWith(SpringRunner.class)
public class ExampleServicePlainSpringTest extends TestBase {
@Test
public void whenProcessingDataThenResultShouldBeReturned() {
assertThat(this.underTest.process()).isEqualTo("I have a dependency bean");
}
}
The Cucumber test is executed by this test class stub:
@RunWith(Cucumber.class)
public class ExampleServiceCucumberTest extends TestBase {}
The actual cucumber step definitions are here:
public class CucumberStepDefinitions extends TestBase {
private String result;
@When("I process data")
public void iProcessData() {
result = this.underTest.process();
}
@Then("the result should be returned")
public void checkResult() {
assertThat(result).isEqualTo("I have a dependency bean");
}
}
The .feature file for Cucumber is in the src/test/resources directory under the same package name as the step definitions class:
Feature: Example
Scenario: Example service bean returns dependency
When I process data
Then the result should be returned
Usually when I encountered the „no thread-bound request found“ error, it was because the @WebAppConfiguration
annotation was missing, or when I tried to inject a request-scoped bean into a non-request scoped bean. But that’s not the case here.
What am I doing wrong?
答案1
得分: 1
我能够找出如何解决它;更新后的代码位于问题链接的GitHub仓库中。
当使用SpringRunner
时,请求上下文是在隐式添加到测试的TestExecutionListener
列表中的ServletTestExecutionListener
中初始化的。
初始化发生在该侦听器的beforeTestMethod()
方法中。
然而,正如@M.P.Korsanje在评论中正确指出的(谢谢!),Cucumber没有测试方法,因此beforeTestMethod()
永远不会被执行。
我的解决方法是添加一个自定义的ServletTestExecutionListener
子类作为TestExecutionListener
,将beforeTestClass()
调用委派给beforeTestMethod()
:
public class ClassLevelServletTestExecutionListener extends ServletTestExecutionListener {
@Override
public void beforeTestClass(TestContext testContext) throws Exception {
super.beforeTestMethod(testContext);
}
@Override
public void afterTestClass(TestContext testContext) throws Exception {
super.afterTestMethod(testContext);
}
}
并且在ExampleServiceCucumberTest
中:
@ContextConfiguration(classes = {AppConfig.class})
@CucumberContextConfiguration
@WebAppConfiguration
@TestExecutionListeners(ClassLevelServletTestExecutionListener.class)
// 扩展Spring类以获取默认的TestExecutionListeners
public class TestBase extends AbstractJUnit4SpringContextTests {
@Autowired
public ExampleService underTest;
}
英文:
I was able to figure out how to resolve it; the updated code is in the github repository linked in the question.
When using the SpringRunner
, the request context is initialized in a ServletTestExecutionListener
that is implicitly added to the list of TestExecutionListener
s for the test.
The initialization happens in the beforeTestMethod()
method of that listener.
However, as @M.P.Korsanje correctly remarked in the comments (thanks!), Cucumber doesn't have test methods, so beforeTestMethod()
is never executed.
My solution was to add a custom subclass of ServletTestExecutionListener
as a TestExecutionListener
that delegates the beforeTestClass()
call to the beforeTestMethod()
:
public class ClassLevelServletTestExecutionListener extends ServletTestExecutionListener {
@Override
public void beforeTestClass(TestContext testContext) throws Exception {
super.beforeTestMethod(testContext);
}
@Override
public void afterTestClass(TestContext testContext) throws Exception {
super.afterTestMethod(testContext);
}
}
And in ExampleServiceCucumberTest
:
@ContextConfiguration(classes = {AppConfig.class})
@CucumberContextConfiguration
@WebAppConfiguration
@TestExecutionListeners(ClassLevelServletTestExecutionListener.class)
// extend the Spring class to get the default TestExecutionListeners
public class TestBase extends AbstractJUnit4SpringContextTests {
@Autowired
public ExampleService underTest;
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论