0

I am doing a unit test for my controller using @WebMvcTest in my unit test. My controller retrieves RequestAttributes data from RequestContextHolder:

RequestContextHolder.currentRequestAttributes().getAttribute(name, RequestAttributes.SCOPE_REQUEST)

The data stored here is set in the interceptor:

RequestAttributes reqAttr = RequestContextHolder.currentRequestAttributes();
reqAttr.setAttribute(name, value, RequestAttributes.SCOPE_REQUEST);
RequestContextHolder.setRequestAttributes(reqAttr, true);

In my controller unit test, the interceptor is not running so I tried to set the request attributes before running my test but I am getting this 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: In this case, use RequestContextListener or RequestContextFilter to expose the current request.

I have tried this answer https://stackoverflow.com/a/9419859/5545327. I added:

@Mock
private RequestAttributes attrs;

@Before
public void before() {
    MockitoAnnotations.initMocks(this);
    RequestContextHolder.setRequestAttributes(attrs);
}

@Test
public void testController() {
    when(requestAttributes.getAttribute(eq("<attribute name>"),
        eq(RequestAttributes.SCOPE_REQUEST))).thenReturn(<mockReturn>);
    // do your test...
}

But it is not working. The error is gone but the controller is getting null value from the RequestContextHolder. By the way I am testing my controller by doing this:

mockMvc.perform(post("<url>").content("<post params here>")).andExpect(status().isOk());

where mockMvc is from @WebMvcTest.

Note: I have a different class for integration test that is why I am using @WebMvcTest here instead of @SpringBootTest

I am hoping you can give a descriptive answer since I am also quite new to Spring.

Thanks!

Edit:

  • I can't simply put the data in the actual HttpRequest attribute. If I am not mistaken, the HttpRequest attributes are not automatically added as a request attribute in RequestContextHolder. Also, the data that I need is not from the HttpRequest but based on the HttpRequest. And I am mocking it since I know the actual request since this is my unit test.

Code looks like this:

@RunWith(SpringRunner.class)
@WebMvcTest
@OverrideAutoConfiguration(enabled = true)
@ActiveProfiles("test")
public abstract class BaseControllerTest {
    @Autowired
    protected MockMvc mockMvc;
}

public class ControllerTest extends BaseControllerTest {
    @Test
    public void testControllerMethod() {
        mockMvc.perform(post("<url>").content("<post params here>")).andExpect(status().isOk());
        // some more codes here
    }
}

public class Controller {
    @PostMapping
    public String controllerMethod() {
        // some more codes here
        Object myObject = RequestContextHolder.currentRequestAttributes().getAttribute(name, RequestAttributes.SCOPE_REQUEST);
        // some more codes here
    }
}
jngacayan
  • 55
  • 10
  • post the code of the controller that you're trying to unit test, and the code of the test that you have now, even if not working – JB Nizet Dec 28 '18 at 08:42
  • OK. Your code is untestable, because you're relying on global static state, which is exactly the kind of stuff that makes testing hard, and that is avoided by using dependency injection, which Spring allows doing in the first place. If you want to access request attributes, then just pass the HttpServletRequest as argument to the method, and get the attributes from the request. Your filter can set the attributes in the request. Or the filter and the controller can both use an autowired request-scope bean to store the request-bound data, and you can create a mock bean in your unit test. – JB Nizet Dec 28 '18 at 08:58

1 Answers1

0

You can set the request attributes when you are performing the call with mockMvc.

Based on your test code, you can modify it to:

public class ControllerTest extends BaseControllerTest {
    @Test
    public void testControllerMethod() {
        mockMvc.perform(post("<url>")
            .content("<post params here>"))
            .requestAttr("my-request-attribute", "some-value")
            .andExpect(status().isOk());
        // some more codes here
    }
}



public class Controller {
    @PostMapping
    public String controllerMethod() {
        // some more codes here
        Object myObject = RequestContextHolder
            .currentRequestAttributes()
            .getAttribute("my-request-attribute", RequestAttributes.SCOPE_REQUEST);
        // some more codes here
    }
}

I think your first method of solving it (i.e. setting your RequestContextHolder's attribute directly) only applies if there were no actual request happening (e.g. testing service layer).

In your case, since we are dealing with mock MVC here, this means that a mocked request is being setup, which comes with it's own request context (and thus it's own request context attributes).

Hope this helps!