ronin667
ronin667

Reputation: 423

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.

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

Answers (1)

ronin667
ronin667

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

Related Questions