对依赖于请求上下文的方法进行单元测试


71

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

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

我收到以下错误:

java.lang.IllegalStateException:找不到线程绑定的请求:您是在实际的Web请求之外引用请求属性,还是在原始接收线程之外处理请求?如果您实际上是在Web请求中操作并且仍然收到此消息,则您的代码可能在DispatcherServlet / DispatcherPortlet之外运行:在这种情况下,请使用RequestContextListener或RequestContextFilter公开当前请求。

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

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

非常感谢你。

Answers:


191

春季测试有一个名为MockHttpServletRequest的灵活请求模拟。

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

您可能需要向javax.servlet:javax.servlet-api添加显式依赖项才能进行上述编译(Spring 5不会将其引入)
djb


5

我能够做与此相同的答案,但没有MockHttpServletRequest使用类@Mock注释。我想它们是相似的。只是在这里张贴给以后的访客。

@Mock
HttpServletRequest request;

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

2

假设您的课程是这样的:

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

如果您没有能力更改使用的类RequestContextHolder,则可以RequestContextHolder在测试代​​码中覆盖该类。也就是说,您在相同的包中创建一个具有相同名称的类,并确保在实际的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。第二种解决方案是我考虑过的解决方案,但是我一直在寻找一种无需更改主代码的解决方案...如果没有人建议其他更好的解决方案,我将使用此解决方案!
satoshi 2012年

1

如果该方法包含:

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

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

然后,您可以删除麻烦制造者部件StringRequestContextHolder.currentRequestAttributes()并直接使用HttpSession

然后MockHttpSession,在测试中使用模拟的Session()对象应该非常容易。

@RequestMapping...
public ModelAndView(... HttpSession session) {
    String id = session.getId();
    ...
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.