【问题标题】:Unit testing a method dependent to the request context对依赖于请求上下文的方法进行单元测试
【发布时间】:2012-02-23 19:12:14
【问题描述】:

我正在为包含以下行的方法编写单元测试:

String sessionId = RequestContextHolder.currentRequestAttributes().getSessionId();

我收到以下错误:

java.lang.IllegalStateException:未找到线程绑定请求:是 您指的是实际 Web 请求之外的请求属性, 或在原始接收线程之外处理请求?如果 你实际上是在一个网络请求中操作并且仍然收到这个 消息,您的代码可能在 DispatcherServlet/DispatcherPortlet:在这种情况下,使用 RequestContextListener 或 RequestContextFilter 暴露当前 请求。

原因很明显——我没有在请求上下文中运行测试。

问题是,如何在测试环境中测试包含对依赖于请求上下文的方法的调用的方法?

非常感谢。

【问题讨论】:

    标签: spring spring-mvc


    【解决方案1】:

    Spring-test 有一个灵活的请求模拟,称为 MockHttpServletRequest。

    MockHttpServletRequest request = new MockHttpServletRequest();
    RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
    

    【讨论】:

    • 您可能需要向 javax.servlet:javax.servlet-api 添加显式依赖项才能编译上述内容(Spring 5 不会引入)
    【解决方案2】:

    您可以模拟/存根 RequestAttributes 对象以返回您想要的内容,然后在开始测试之前使用您的模拟/存根调用 RequestContextHolder.setRequestAttributes(RequestAttributes)

    @Mock
    private RequestAttributes attrs;
    
    @Before
    public void before() {
        MockitoAnnotations.initMocks(this);
        RequestContextHolder.setRequestAttributes(attrs);
    
        // do you when's on attrs
    }
    
    @Test
    public void testIt() {
        // do your test...
    }
    

    【讨论】:

      【解决方案3】:

      我可以做与这个answer 相同的事情,但没有使用@Mock 注释的MockHttpServletRequest 类。我猜他们很相似。只是在这里为未来的访问者发帖。

      @Mock
      HttpServletRequest request;
      
      @Before
      public void setup() {
          MockitoAnnotations.initMocks(this);
          RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
      }
      

      【讨论】:

      • 这对我有用,使用 @RequestScope bean 并使用 WireMockServer 进行测试。
      【解决方案4】:

      假设你的班级是这样的:

      class ClassToTest {
          public void doSomething() {
              String sessionId = RequestContextHolder.currentRequestAttributes().getSessionId();
              // Do something with sessionId
          }
      }
      

      如果您无法更改使用RequestContextHolder 的类,那么您可以在测试代码中覆盖RequestContextHolder 类。 IE。你在同一个包中创建一个同名的类,并确保它在实际的 Spring 类之前加载。

      package org.springframework.web.context.request;
      
      public class RequestContextHolder {
          static RequestAttributes currentRequestAttributes() {
              return new MyRequestAttributes();
          }
      
          static class MyRequestAttributes implements RequestAttributes {
              public String getSessionId() {
                  return "stub session id";
              }
              // Stub out the other methods.
          }
      }
      

      现在,当您的测试运行时,它们会选择您的 RequestContextHolder 类并优先使用该类而不是 Spring 类(假设为发生这种情况设置了类路径)。 这不是让您的测试运行的特别好的方法,但如果您无法更改您正在测试的类,则可能需要这样做。

      或者,您可以将会话 ID 检索隐藏在抽象后面。比如引入一个接口:

      public interface SessionIdAccessor {
          public String getSessionId();
      }
      

      创建一个实现:

      public class RequestContextHolderSessionIdAccessor implements SessionIdAccessor {
          public String getSessionId() {
              return RequestContextHolder.currentRequestAttributes().getSessionId();
          }
      }
      

      并在你的类中使用抽象:

      class ClassToTest {
          SessionIdAccessor sessionIdAccessor;
      
          public ClassToTest(SessionIdAccessor sessionIdAccessor) {
              this.sessionIdAccessor = sessionIdAccessor;
          }
      
          public void doSomething() {
              String sessionId = sessionIdAccessor.getSessionId();
              // Do something with sessionId
          }
      }
      

      然后您可以为您的测试提供一个虚拟实现:

      public class DummySessionIdAccessor implements SessionIdAccessor {
          public String getSessionId() {
              return "dummy session id";
          }
      }
      

      这类事情突出了一种通常的最佳实践,即在抽象背后隐藏某些环境细节,以便在环境发生变化时将它们换掉。 这同样适用于通过将虚拟实现替换为“真实”实现来降低测试的脆弱性。

      【讨论】:

      • 谢谢你,@PaulGrime。第二种解决方案是我考虑过的,但我一直在寻找一种无需更改主代码的解决方案……如果没有人提出另一种更好的解决方案,我将使用此解决方案!
      【解决方案5】:

      如果方法包含:

      String sessionId = RequestContextHolder.currentRequestAttributes().getSessionId();
      

      是 web 控制器方法,那么我建议更改方法签名,以便您/spring 将请求作为单独的参数传递给方法。

      然后您可以删除麻烦制造者部分字符串RequestContextHolder.currentRequestAttributes()并直接使用HttpSession

      那么在测试中使用模拟的 Session (MockHttpSession) 对象应该很容易。

      @RequestMapping...
      public ModelAndView(... HttpSession session) {
          String id = session.getId();
          ...
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-09-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多