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

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

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

我得到以下错误:

Lang.IllegalStateException: 找不到线程绑定请求: Are 你引用一个实际的 web 请求之外的请求属性, 或在原始接收线程之外处理请求 你实际上是操作在一个网络请求,仍然收到这个 消息,您的代码可能运行在 DispatcherServlet/DispatcherPortlet: 在本例中,使用 RequestContextListener 或 RequestContextFilter 来公开当前 请求。

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

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

非常感谢。

50866 次浏览

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

假设你的类是这样的:

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

这种情况突出显示了一种常见的最佳实践,即在抽象背后隐藏某些环境细节,以便在环境发生变化时可以将其替换掉。 这同样适用于将虚拟实现替换为“真实”实现,从而降低测试的脆弱性。

如果该方法包含:

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

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

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

那么在测试中使用模拟会话(MockHttpSession)对象应该非常容易。

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

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

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

我能够像这个 回答一样做同样的事情,但是没有使用 @Mock注释的 MockHttpServletRequest类。我想他们很相似。只是在这里为未来的访客张贴。

@Mock
HttpServletRequest request;


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