Unit testing a method dependent to the request context

SpringSpring Mvc

Spring Problem Overview


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.

Spring Solutions


Solution 1 - Spring

Spring-test has a flexible request mock called MockHttpServletRequest.

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

Solution 2 - Spring

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

Solution 3 - Spring

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

Solution 4 - Spring

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.

Solution 5 - Spring

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();
    ...
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionsatoshiView Question on Stackoverflow
Solution 1 - SpringpapView Answer on Stackoverflow
Solution 2 - Springnicholas.hauschildView Answer on Stackoverflow
Solution 3 - SpringmaazadeebView Answer on Stackoverflow
Solution 4 - SpringPaul GrimeView Answer on Stackoverflow
Solution 5 - SpringRalphView Answer on Stackoverflow