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

发布于 2025-01-08 18:46:52 字数 516 浏览 0 评论 0原文

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

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

我收到以下错误:

java.lang.IllegalStateException:未找到线程绑定请求:是 您指的是实际 Web 请求之外的请求属性, 或者在最初接收线程之外处理请求?如果 您实际上是在网络请求中进行操作并且仍然收到此消息 消息,您的代码可能在外部运行 DispatcherServlet/DispatcherPortlet:在本例中,使用 RequestContextListener 或 RequestContextFilter 暴露当前 请求。

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

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

非常感谢。

I'm writing a unit test for a method that contains the following line:

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

I get the following error:

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.

The reason is quite obvious — I'm not running the test in a request context.

The question is, how can I test a method that contains a call to a method dependent to the request context in a test environnment?

Thank you very much.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

晌融 2025-01-15 18:46:53

假设您的类类似于:

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

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

Assuming your class is something like:

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

If you don't have the ability to change the class that uses RequestContextHolder, then you could override the RequestContextHolder class in your test code.
I.e. you create a class with the same name, in the same package, and ensure it is loaded before the actual Spring class.

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

Now, when your tests run, they will pick up your RequestContextHolder class and use that in preference to the Spring one (assuming the classpath is set up for this to happen).
This isn't a particular nice way of getting your tests to run, but it might be necessary if you can't change the class you are testing.

Alternatively, you could hide the session id retrieval behind an abstraction. For example introduce an interface:

public interface SessionIdAccessor {
    public String getSessionId();
}

Create an implementation:

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

And use the abstraction in your class:

class ClassToTest {
    SessionIdAccessor sessionIdAccessor;

    public ClassToTest(SessionIdAccessor sessionIdAccessor) {
        this.sessionIdAccessor = sessionIdAccessor;
    }

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

Then you can provide a dummy implementation for your tests:

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

This sort of thing highlights a usual best-practice to hide certain environmental details behind abstractions so that you can swap them out if your environment changes.
This applies equally to making your tests less brittle by swapping dummy implementations for 'real' ones.

梦途 2025-01-15 18:46:53

如果方法包含:

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

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

然后你可以删除麻烦部分 String RequestContextHolder.currentRequestAttributes() 并直接使用 HttpSession

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

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

If the method containing:

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

is the web controller method, then I would recommend to change the method signature, so that you/spring pass the Request as an seperate paremter to the method.

Then you can remove the troublemaker part String RequestContextHolder.currentRequestAttributes() and use the HttpSession direcly.

Then it should be very easy to use a mocked Session (MockHttpSession) object in the test.

@RequestMapping...
public ModelAndView(... HttpSession session) {
    String id = session.getId();
    ...
}
禾厶谷欠 2025-01-15 18:46:52

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

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

Spring-test has a flexible request mock called MockHttpServletRequest.

MockHttpServletRequest request = new MockHttpServletRequest();
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
绅士风度i 2025-01-15 18:46:52

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

You can mock/stub the RequestAttributes object to return what you want, and then call RequestContextHolder.setRequestAttributes(RequestAttributes) with your mock/stub before you start your test.

@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...
}
灯下孤影 2025-01-15 18:46:52

我能够执行与此 answer 相同的操作,但没有使用 MockHttpServletRequest 类code>@Mock 注解。我猜他们很相似。只是在这里发布以供未来的访客使用。

@Mock
HttpServletRequest request;

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

I was able to do the same as this answer, but without the MockHttpServletRequest class using the @Mock annotation. I guess they're similar. Just posting here for future visitors.

@Mock
HttpServletRequest request;

@Before
public void setup() {
    MockitoAnnotations.initMocks(this);
    RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文