25

In my tests I setup the MockMvc object in the @Before like this

mockMvc = MockMvcBuilders.webAppContextSetup(context)
                .apply(springSecurity())
                .build();

In every request I do I always need to send the same headers. Is there a way to configure the headers the MockMvc will use globally or per test class?

isADon
  • 3,433
  • 11
  • 35
  • 49

4 Answers4

18

I do not know if it still relevant, but I stumbled over the same problem. We added an API key authentication to a REST api afterwards, and all tests (mainly with @AutoConfigureMockMvc) needed to be adjusted with using a proper API (on top of the new tests, testing that the keys are working).

Spring uses their Customizers and Builders pattern also when creating the MockMvc, like it is done with RestTemplateBuilder and RestTemplateCustomizer.

You are able to create a @Bean/@Component that is a org.springframework.boot.test.autoconfigure.web.servlet.MockMvcBuilderCustomizerand it will get picked up during the bootstrap process of your @SpringBootTests.

You can then add a parent defaultRequetsBuilders that are merged with the specific RequestBuilders when running the test.

Sample Customizer that adds a header

package foobar;


import org.springframework.boot.test.autoconfigure.web.servlet.MockMvcBuilderCustomizer;
import org.springframework.stereotype.Component;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;

/**
 * Whenever a mockmvc object is autoconfigured, this customizer should be picked up, and a default, usable, working, valid api key is set as
 * default authorization header to be applied on all tests if not overwritten.
 *
 */
@Component
public class ApiKeyHeaderMockMvcBuilderCustomizer implements MockMvcBuilderCustomizer {

    @Override
    public void customize(ConfigurableMockMvcBuilder<?> builder) {
        // setting the parent (mergeable) default requestbuilder to ConfigurableMockMvcBuilder
        // every specifically set value in the requestbuilder used in the test class will have priority over
        // the values set in the parent. 
        // This means, the url will always be replaced, since "any" would not make any sense.
        // In case of multi value properties (like headers), existing headers from our default builder they are either merged or appended,
        // exactly what we want to achieve
        // see https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/autoconfigure/web/servlet/MockMvcBuilderCustomizer.html
        // and https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/Mergeable.html
        RequestBuilder apiKeyRequestBuilder = MockMvcRequestBuilders.get("any")
            .header("api-key-header", "apikeyvalue");
        builder.defaultRequest(apiKeyRequestBuilder);
    }

}

Hope that helps.

BigDaddy
  • 441
  • 6
  • 5
  • Worked for me. Thanks! – user2347638 May 25 '21 at 21:22
  • 2
    update: when I upgraded to Spring 2.4.4 its seems there are now validators, and the builder validates the passed URL, so "any" does not work anymore. You need to insert a valid url, which one does not matter since it will be merged/overwritten during the tests – BigDaddy Jul 02 '21 at 08:33
15

How about you make a factory class to start you off with your already decrorated-with-headers request? Since MockHttpServletRequestBuilder is a builder, you just decorate the request with any of the additional properties (params, content type, etc.) that you need. The builder is designed just for this purpose! For example:

public class MyTestRequestFactory {

    public static MockHttpServletRequestBuilder myFactoryRequest(String url) {
        return MockMvcRequestBuilders.get(url)
                .header("myKey", "myValue")
                .header("myKey2", "myValue2");
    }
}

Then in your test:

@Test
public void whenITestUrlWithFactoryRequest_thenStatusIsOK() throws Exception {

    mockMvc()
        .perform(MyTestRequestFactory.myFactoryRequest("/my/test/url"))
        .andExpect(status().isOk());
}

@Test
public void whenITestAnotherUrlWithFactoryRequest_thenStatusIsOK() throws Exception {

    mockMvc()
        .perform(MyTestRequestFactory.myFactoryRequest("/my/test/other/url"))
        .andExpect(status().isOk());
}

Each test will call the endpoint with the same headers.

Dovmo
  • 8,121
  • 3
  • 30
  • 44
  • 2
    Thats one option. I just don't really like that I need to create a factory method for each of the HTTP request methods even though the all take the same header, – isADon May 25 '18 at 16:27
  • You can use the same factory method for each request; this would decorate each request with the same headers on each call to the factory method – Dovmo May 25 '18 at 16:37
3

You can write an implementation of javax.servlet.Filter. In your case, you can add the headers into your request. MockMvcBuilders has a method to add filters:

mockMvc = MockMvcBuilders.webAppContextSetup(context)
            .apply(springSecurity())
            .addFilter(new CustomFilter(), "/*")
            .build();
Nic
  • 6,211
  • 10
  • 46
  • 69
borino
  • 1,720
  • 1
  • 16
  • 24
  • 6
    This answer would be significantly improved by at least a couple of sentences on how to _make_ that filter. It's a lot more complex than it seems at first. – Nic Aug 17 '18 at 19:42
2
this.mockMvc = MockMvcBuilders.webAppContextSetup(context).apply(new HttpHeaderMockMvcConfigurer()).build();

public class HttpHeaderMockMvcConfigurer extends MockMvcConfigurerAdapter {
    @Override
    public RequestPostProcessor beforeMockMvcCreated(ConfigurableMockMvcBuilder<?> builder, WebApplicationContext cxt) {
        builder.defaultRequest(MockMvcRequestBuilders.post("test").header("appId", "aaa"));
        return super.beforeMockMvcCreated(builder, cxt);
    }
}

Define default request properties that should be merged into all performed requests. In effect this provides a mechanism for defining common initialization for all requests such as the content type, request parameters, session attributes, and any other request property.

minjay26
  • 21
  • 3