Request-scoped beans not working in Spring tests with Cucumber.

huangapple go评论82阅读模式
英文:

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

huangapple
  • 本文由 发表于 2020年8月12日 17:41:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/63373893.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定