Test The Spring MVC Test framework, also known as MockMvc, provides support for testing Spring MVC applications. It does full Spring MVC request processing through mock request and response objects instead of a running server.

MockMvc can be used separately to make requests and validate responses. It can also be used via WebTestClient, where MockMvc is connected as a server to process requests. The advantage of WebTestClient is the ability to work with higher-level objects instead of raw data, and the ability to move to full, end-to-end HTTP tests on a real server using the same testing API.

Brief Description

You can write simple unit tests for Spring MVC by instantiating a controller, attaching dependencies to it, and calling its methods. However, such tests do not check request mappings, data binding, message conversion, type conversion, validation, and do not use any of the helper methods annotated with @InitBinder, @ModelAttribute, or @ExceptionHandler.

The Spring MVC Test framework, also known as MockMvc, aims to provide more comprehensive testing of Spring MVC controllers without a running server. It does this by calling DispatcherServlet and passing mock servlet API implementations from the spring-test module, which reproduces the full processing of Spring MVC requests without a running server.

MockMvc is a server-side testing framework that allows you to test most of the functionality of a Spring MVC application using lightweight and targeted tests. You can use it separately to make requests and check responses, or you can use it through the WebTestClient API with MockMvc connected as a server to process requests.

Static import

When using MockMvc directly to execute requests, you will need to perform static import:

  • MockMvcBuilders.*

  • MockMvcRequestBuilders.*

  • MockMvcResultMatchers.*

  • MockMvcResultHandlers.*

An easy way to remember them is to search for MockMvc*. If you're using Eclipse, be sure to also add the above static members to your "favorites" in Eclipse preferences.

When using MockMvc via WebTestClient, you won't need static importing. WebTestClient provides a fluid API without static importing.

Setting options

MockMvc can be configured in one of two ways. One is to point directly at the controllers that need to be tested and programmatically configure the Spring MVC framework. The second is to point to the Spring configuration when using the Spring MVC framework and the controller framework within it.

To configure MockMvc to test a specific controller, use the following:

Java

class MyWebTests {
    MockMvc mockMvc;
    @BeforeEach
    void setup() {
        this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();
    }
    // ...
}>
Kotlin

class MyWebTests {
    lateinit var mockMvc : MockMvc
    @BeforeEach
    fun setup() {
        mockMvc = MockMvcBuilders.standaloneSetup(AccountController()).build()
    }
    // ...
}

Or you can also use this setup when testing via WebTestClient, which delegates authority to the same build tool, as shown above.

To configure MockMvc via Spring configuration, use the following:

Java

@SpringJUnitWebConfig(locations = "my-servlet-context.xml")
class MyWebTests {
    MockMvc mockMvc;
    @BeforeEach
    void setup(WebApplicationContext wac) {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
    }
    // ...
}
Kotlin

@SpringJUnitWebConfig(locations = ["my-servlet-context.xml"])
class MyWebTests {
    lateinit var mockMvc: MockMvc
    @BeforeEach
    fun setup(wac: WebApplicationContext) {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build()
    }
    // ...
}

Or you can also use this setting when testing via WebTestClient which delegates authority to the same build tool as shown above.

Which setup option should you use?

webAppContextSetup loads the actual Spring MVC configuration, allowing for a more complete integration test. Because the TestContext framework caches the loaded Spring configuration, it helps you execute tests quickly even as you introduce more tests into your test suite. Moreover, you can inject mock services into controllers using Spring configuration to focus on web tier testing. In the following example, the mocked service is declared using Mockito:


<bean id="accountService" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="org.example.AccountService"/>
</bean>

You can then inject the mock service into a test to configure and test your expected events, as shown in the following example:

Java

@SpringJUnitWebConfig(locations = "test-servlet-context.xml")
class AccountTests {
    @Autowired
    AccountService accountService;
    MockMvc mockMvc;
    @BeforeEach
    void setup(WebApplicationContext wac) {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }
    // ...
}
Kotlin

@SpringJUnitWebConfig(locations = ["test-servlet-context.xml"])
class AccountTests {
    @Autowired
    lateinit var accountService: AccountService
    lateinit mockMvc: MockMvc
    @BeforeEach
    fun setup(wac: WebApplicationContext) {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build()
    }
    // ...
}

The standaloneSetup setup, on the other hand, is a little more suitable for unit testing. She tests one controller at a time. It is possible to manually inject mock dependencies into the controller without requiring Spring configuration to be loaded. Such tests are more style-oriented and allow you to understand which controller is being tested, whether any specific Spring MVC configuration is required to work, and so on. Setting up standaloneSetup is also a very convenient way to write custom tests to test specific logic or debug an issue.

As with most of the integration testing vs. unit testing debates, there is no correct or incorrect answer. However, using standaloneSetup implies the need for additional tests using webAppContextSetup to validate the Spring MVC configuration. Additionally, you can write all your tests using webAppContextSetup to always test against a real Spring MVC configuration.

Setup Features

No matter which tool Assemblies from MockMvc you use, all implementations of MockMvcBuilder will provide some common and extremely useful functions. For example, you would be able to declare a Accept header on all requests and accept status 200, as well as a Content-Type header on all responses, as shown below:

Java

// static import MockMvcBuilders.standaloneSetup
MockMvc mockMvc = standaloneSetup(new MusicController())
    .defaultRequest(get("/").accept(MediaType.APPLICATION_JSON))
    .alwaysExpect(status().isOk())
    .alwaysExpect(content().contentType("application/json;charset=UTF-8"))
    .build();
Kotlin
// Not possible in Kotlin until is fixed

Also, third-party frameworks (and applications) may pre-package configuration instructions, for example in MockMvcConfigurer. Spring Framework has one such built-in implementation which helps to save and reuse the HTTP session across all requests. You can use it like this:

Java

// static import SharedHttpSessionConfigurer.sharedHttpSession
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new TestController())
    .apply(sharedHttpSession())
    .build();
// Import mockMvc to execute queries...
Kotlin
// Not possible in Kotlin until will not be fixed

See. javadoc by ConfigurableMockMvcBuilder for a list of all the capabilities of the MockMvc builder, or use the IDE to explore the available options.

Running queries

In This section shows how to use MockMvc separately to make requests and validate responses. If you are using MockMvc via WebTestClient, please refer to the appropriate section on writing tests.

To run requests using any HTTP method, rely on the example shown below:

Java

// static import MockMvcRequestBuilders.*.
mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON));
Kotlin

import org.springframework.test.web.servlet.post
mockMvc.post("/hotels/{id}", 42) {
    accept = MediaType.APPLICATION_JSON
}

It is also possible to make file upload requests that internally use MockMultipartHttpServletRequest to avoid actually parsing the multipart request. Rather, you will have to configure it as shown in the following example:

Java
mockMvc.perform(multipart("/doc").file("a1" , "ABC".getBytes("UTF-8")));
Kotlin

import org.springframework.test.web.servlet.multipart
mockMvc.multipart("/doc") {
    file("a1", "ABC".toByteArray(charset("UTF8")))
}

You can specify request parameters in URI pattern style, as shown in the following example:

Java
mockMvc.perform(get ("/hotels?thing={thing}", "somewhere"));
Kotlin
mockMvc .get("/hotels?thing={thing}", "somewhere")

You can also add servlet request parameters, which are either request parameters , or form parameters, as shown in the following example:

Java
mockMvc.perform(get("/hotels").param("thing", " somewhere"));
Kotlin

import org.springframework.test.web.servlet.get
mockMvc. get("/hotels") {
    param("thing", "somewhere")
}

If the application code uses servlet request parameters and does not check the query string explicitly (which is what happens most often), then it doesn't matter which option you use. However, be aware that request parameters provided using the URI pattern are decoded, while request parameters provided through the param(...) method are expected to already be decoded.

In most cases, it is preferable to omit the context path and servlet path from the request URI. If you need to test with the full request URI, be sure to set contextPath and servletPath appropriately so that request matching works, as shown in the following example:

Java
mockMvc.perform(get("/app/main/hotels/{id}").contextPath("/app").servletPath("/main"))
Kotlin

import org.springframework.test.web.servlet.get
mockMvc.get("/ app/main/hotels/{id}") {
    contextPath = "/app"
    servletPath = "/main"
}

In the above example it would be inconvenient set contextPath and servletPath on every request made. Instead, you can set default query properties, as shown in the following example:

Java

class MyWebTests {
    MockMvc mockMvc;
    @BeforeEach
    void setup() {
        mockMvc = standaloneSetup(new AccountController())
            .defaultRequest(get("/")
            .contextPath("/app").servletPath("/main")
            .accept(MediaType.APPLICATION_JSON)).build();
    }
}
Kotlin
// Not possible in Kotlin until will not be fixed

The preceding properties affect every request made through the MockMvc instance. If the same property is also specified in a given request, it overrides the default value. Therefore, the default HTTP method and URI in the request are irrelevant because they must be specified for each request.

Defining Expected Events

You can define expected events by adding one or multiple calls to andExpect(..) after the request has completed, as shown in the following example. If any one expected event does not occur, other expected events will not be confirmed.

Java

// static import MockMvcRequestBuilders.*. and MockMvcResultMatchers.*
mockMvc.perform(get("/accounts/1")).andExpect(status().isOk());
Kotlin

import org.springframework.test.web.servlet.get
mockMvc.get("/accounts/1").andExpect {
    status { isOk() }
}

You can define multiple expected events by adding andExpectAll(..) after the request is executed, as shown in the following example. Unlike andExpect(..), andExpectAll(..) ensures that all specified expected events are confirmed and any failed events are monitored and reported.

Java

// static import MockMvcRequestBuilders.*. and MockMvcResultMatchers.*
mockMvc.perform(get("/accounts/1")).andExpectAll(
    status().isOk(),
    content().contentType("application/json;charset=UTF-8"));

MockMvcResultMatchers.* specifies a series of expected events, some of which are further nested within more detailed expected events.

Expected events fall into two general categories. The first category of assertions checks response properties (such as response status, headers, and content). These are the most important results that can be supported.

The second category of statements goes beyond the scope of the answer. These assertions allow you to check specific aspects of Spring MVC, such as which controller method handled the request, whether an exception was thrown and handled, what the contents of the model are, what view was selected, what flash attributes were added, and so on. They also allow you to test specific aspects of servlets, such as request and session attributes.

The following test confirms that binding or validation failed:

Java

mockMvc.perform(post("/persons"))
    .andExpect(status().isOk())
    .andExpect(model().attributeHasErrors("person"));
Kotlin

import org.springframework.test.web.servlet.post
mockMvc.post("/persons").andExpect {
    status { isOk() }
    model {
        attributeHasErrors("person")
    }
}

Often when writing tests, it is useful to unload the results of the executed query. This can be done in the following way, where print() is a static imported element from MockMvcResultHandlers:

Java

mockMvc.perform(post("/persons"))
    .andDo(print())
    .andExpect(status().isOk())
    .andExpect(model().attributeHasErrors("person"));
Kotlin

import org.springframework.test.web.servlet.post
mockMvc.post("/persons").andDo {
        print()
    }.andExpect {
        status { isOk() }
        model {
            attributeHasErrors("person")
        }
    }

As long as the request is processed will not throw an unhandled exception, the print() method will output all available result data to System.out. There is also a log() method and two additional variants of the print() method, one of which takes an OutputStream and the other takes a Writer. For example, calling print(System.err) outputs the result data to System.err, and calling print(myWriter) outputs the result data to a special method records (writer). If you want the result data to be logged rather than printed, you can call the log() method, which writes the result data as a single DEBUG message in the logging category org.springframework.test.web.servlet.result.

In some cases, you may want to directly access the result and test something that cannot be tested in any other way. This can be accomplished by adding .andReturn() after all other expected events, as shown in the following example:

Java

MvcResult mvcResult = mockMvc.perform(post("/persons")).andExpect(status().isOk()).andReturn();
// ...
Kotlin

var mvcResult = mockMvc.post("/persons").andExpect { status { isOk() } }.andReturn()
// ...

If all tests repeat the same expected events, then you can set common events expected once when instantiating MockMvc, as shown in the following example:

Java

standaloneSetup(new SimpleController())
    .alwaysExpect(status().isOk())
    .alwaysExpect(content().contentType("application/json;charset=UTF-8"))
    .build()
Kotlin
// Not possible in Kotlin until is fixed

Note that generic expect events are always applied and cannot be overridden without creating a separate instance of MockMvc.

If the JSON response content contains hypermedia links created using Spring HATEOAS, you can validate the resulting links using JsonPath expressions, as shown in the following example:

Java

mockMvc.perform(get("/people").accept (MediaType.APPLICATION_JSON))
    .andExpect(jsonPath("$.links[?(@.rel == 'self')].href").value("http://localhost:8080/people"));
Kotlin

mockMvc.get("/people") {
    accept(MediaType.APPLICATION_JSON)
}.andExpect {
    jsonPath("$.links[?(@.rel == 'self')].href") {
        value("http://localhost:8080/people")
    }
}

If the XML response content contains hypermedia links created using Spring HATEOAS , you can check the received links using XPath expressions:

Java

Map<String, String> ns = Collections.singletonMap("ns", "http://www.w3.org/2005/Atom");
mockMvc.perform(get("/handle").accept(MediaType.APPLICATION_XML))
    .andExpect(xpath("/person/ns:link[@rel='self']/@href", ns).string("http://localhost:8080/people"));
Kotlin

val ns = mapOf("ns" to "http://www.w3.org/2005/Atom")
mockMvc.get("/handle") {
    accept(MediaType.APPLICATION_XML)
}.andExpect {
    xpath("/person/ns:link[@rel='self']/@href", ns) {
        string("http://localhost:8080/people")
    }
}

Asynchronous requests

This section shows how to use MockMvc separately to test asynchronous request processing. When using MockMvc via WebTestClient there is nothing special about getting asynchronous requests to work, since WebTestClient automatically does what is described in this section.

Async Servlet 3.0 requests, supported in Spring MVC, work by exiting the servlet container thread , thereby allowing the application to evaluate the response asynchronously, after which asynchronous dispatch is performed to complete processing on the servlet container thread.

In Spring MVC Test, asynchronous requests can be tested by first asserting the generated asynchronous value, then manually performing asynchronous dispatch and finally checking the answer. Below is an example test for controller methods that return DeferredResult, Callable, or a reactive type such as Mono from Reactor:

Java

// static import MockMvcRequestBuilders.*. and MockMvcResultMatchers.*
@Test
void test() throws Exception {
    MvcResult mvcResult = this.mockMvc.perform(get("/path"))
            .andExpect(status().isOk()) 
            .andExpect(request().asyncStarted()) 
            .andExpect(request() .asyncResult("body")) 
            .andReturn();
    this.mockMvc.perform(asyncDispatch(mvcResult)) 
            .andExpect(status().isOk()) 
            .andExpect(content().string("body")); }
  1. Response verification status remains unchanged
  2. Asynchronous processing should begin
  3. We wait and confirm the result of asynchronous processing
  4. We manually perform asynchronous dispatch (since there is no running container)
  5. Check the final response
Kotlin

@Test
fun test() {
    var mvcResult = mockMvc.get("/path").andExpect {
        status { isOk() } 
        request { asyncStarted() } 
        // TODO Remove unused generic parameter request { asyncResult<Nothing> ("body") } 
    }.andReturn()
    mockMvc.perform(asyncDispatch(mvcResult)) 
            .andExpect {
                status { isOk() } 
                content().string("body ")
            }
}
  1. Response verification status remains unchanged
  2. Asynchronous processing
  3. We wait and confirm the result of asynchronous processing
  4. We manually perform asynchronous dispatch (since there is no running container)
  5. Check the final response

Stream Responses

The best way to test stream responses such as Server-Sent Events is WebTestClient, which can be used as a test client to connect to a MockMvc instance to run tests on Spring MVC controllers without a running server. For example:

Java

WebTestClient client = MockMvcWebTestClient.bindToController(new SseController()).build();
FluxExchangeResult<Person> exchangeResult = client.get()
        .uri("/persons")
        .exchange()
        .expectStatus().isOk()
        .expectHeader().contentType("text/event-stream")
        .returnResult(Person.class);
// Use StepVerifier from Project Reactor to test the streaming response
StepVerifier.create(exchangeResult.getResponseBody())
        .expectNext(new Person("N0"), new Person("N1"), new Person("N2"))
        .expectNextCount(4)
        .consumeNextWith(person -> assertThat(person.getName()).endsWith("7"))
        .thenCancel()
        .verify();

WebTestClient can also connect to a live server and run full end-to-end integration tests. This is also supported in Spring Boot, where you can Test a running server.

Registering filters

When setting up an instance MockMvc you can register one or more instances of Filter servlets, as shown in the following example:

Java
mockMvc = standaloneSetup(new PersonController()).addFilters(new CharacterEncodingFilter()).build();
Kotlin
// Not possible in Kotlin until is fixed

Registered filters are called via MockFilterChain from spring-test, and the last filter is delegated to DispatcherServlet.

MockMvc vs. End-to-End Tests

MockMvc is built on top of mock Servlet API implementations from the spring-test module and is independent of the running container. So there are some differences compared to full end-to-end integration tests with a real client and a real server.

The easiest way to understand this is to start with an empty MockHttpServletRequest request. Whatever you add to it becomes a request. Things that may catch you by surprise: There is no context path by default; no cookie jsessionid; there is no redirection, errors or asynchronous dispatching; and therefore there is virtually no JSP rendering (visualization). Instead, "redirected" and "forwarded" URLs are stored in MockHttpServletResponse, after which they can be validated with expected events.

This means that if JSP is used, then JSP can be validated -the page to which the request was redirected, but the HTML will not be rendered. In other words, JSP is not called. Note, however, that any other rendering technologies that do not rely on redirection, such as Thymeleaf and Freemarker, pass HTML into the response body as expected. The same is true for rendering JSON, XML, and other formats using methods annotated with @ResponseBody.

Alternatively, you may want to consider Spring Boot's full support for end-to-end integration testing with @SpringBootTest annotations. See "Spring Boot Reference Guide".

Each approach has its pros and cons. The options provided in Spring MVC Test fall at different places on the scale from classic unit testing to full integration testing. Of course, none of Spring MVC Test's options fall into the category of classic unit testing, but they are a little closer to it. For example, you can isolate the web tier by injecting mock services into controllers, in which case the web tier will only be tested via DispatcherServlet, but with the actual Spring configuration, since you can test the data access tier separately from the levels above. Alternatively, you can use a standalone setup by focusing on one controller at a time and manually providing the configuration needed to make it work.

Another important difference when using a test in the Spring MVC Test framework is that, conceptually, the tests are server-side, so you have the ability to check what handle was used, whether the exception was handled by the HandlerExceptionResolver, what the contents of the model were, what binding errors there were, and other details. This means that it becomes easier to write expected events, since the server is not an opaque box, as is the case when testing through an actual HTTP client. In general, the advantage of classic unit testing is that: Such testing is easier to write, justify and debug, but it does not replace the need for full-fledged integration tests. At the same time, it is important not to lose sight of the fact that the answer is the most important thing to check. In short, there is room for multiple testing styles and strategies, even within the same project.

Additional examples

The framework's own tests include many test cases called show how to use MockMvc alone or via WebTestClient Check out these examples to get more ideas.