MockMvc configure a header for all requests

27,716

Solution 1

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.

Solution 2

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.

Solution 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();

Solution 4

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.

Share:
27,716
isADon
Author by

isADon

Updated on August 22, 2020

Comments

  • isADon
    isADon over 3 years

    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
    isADon almost 6 years
    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,
  • Dovmo
    Dovmo almost 6 years
    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
  • Nic
    Nic over 5 years
    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.
  • minjay26
    minjay26 over 4 years
    After my test,the default request uri,method does not affect other test requests.
  • user2347638
    user2347638 almost 3 years
    Worked for me. Thanks!
  • BigDaddy
    BigDaddy almost 3 years
    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