Reputation: 423
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?
Upvotes: 2
Views: 2651
Reputation: 423
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;
}
Upvotes: 1