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

时间:2012-02-23 19:12:14

标签: spring spring-mvc

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

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

我收到以下错误:

  

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

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

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

非常感谢。

5 个答案:

答案 0 :(得分:140)

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

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

答案 1 :(得分:39)

您可以模拟/存根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...
}

答案 2 :(得分:1)

假设您的课程类似于:

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

这种事情突出了一种通常的最佳做法,即隐藏抽象背后的某些环境细节,以便在环境发生变化时将其交换出来。 这同样适用于通过交换“真实”实现的虚拟实现来减少测试的脆弱性。

答案 3 :(得分:1)

如果方法包含:

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

是Web控制器方法,然后我建议更改方法签名,以便/ spring将请求作为单独的paremter传递给方法。

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

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

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

答案 4 :(得分:0)

我可以执行与此answer相同的操作,但是没有使用MockHttpServletRequest注释的@Mock类。我想它们是相似的。只是在这里张贴给以后的访客。

@Mock
HttpServletRequest request;

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