, ,

In my earlier Integration Testing with Spring and JUnit post, I discussed how to test a service bean facade with JUnit. I also posted a video, titled Testing Spring with JUnit on YouTube. In post, Spring Boot Web Application – Part 3 – Spring Data JPA, I showed how to test a Spring Data JPA repository of a Spring MVC application.

It has been more than a year now since I wrote these posts and posted the video. Since then, there’s been an exciting overhaul of testing support for Spring Boot MVC applications. The Spring Boot 1.4 release includes a number of exciting new testing features for our use.

In this post, I will look the new Spring MVC testing features. And I’ll show you how to put them to use.

Spring Boot 1.4 Testing Enhancements

In Spring Boot 1.3, there’s a lot of choices to write tests for a Spring MVC application. One option to set Spring MVC is shown in my earlier post here. In this post on Spring Boot 1.3, the @RunWith annotation with @ContextConfiguration is used to test for a business service façade, like this:

Another method I used in the post here is a combination of the @RunWith annotation with @SpringApplicationConfiguration to test a Spring Data JPA repository, like this:

There are several other approaches you can check in the official Spring blog here.

The testing approaches I used above are actually integration tests.  A pure unit test shouldn’t create and load Spring Context.

Spring Boot 1.4 replaces these various testing approaches that via a single @SpringBootTest annotation for regular integration tests.

Prior to Spring Boot 1.4, I found Spring was lacking a simplified unit testing approach. This is really no surprise. The Spring team is always creating. Spring and Spring Boot offers a number of testing options. Due to innvotation, the testing options have evolved over time. In Spring Boot 1.4, the Spring committers took some time to clean testing up. They gave us much simpler options to use for testing Spring Boot applications. For example, a simple approach to unit test a controller having @Autowired external services without having to load Spring Context was lacking. With Spring Boot 1.4, it’s now possible.

Another missing piece that Spring Boot 1.4 tackels, is the ability to test portions (slices) of code. This can be done without the need to fire up a server. And with out the need to load up the entire Spring Context. Spring Boot 1.4 does this through the new Test Slicing feature that is designed to set-up a narrow slice of the Spring Context. This makes testing single ‘slices’ much easier. You can now focus on testing specific slices of your application. For example:

For example:

  • MVC slice: Controller code through the @WebMvcTest annotation
  • JPA slice: Spring Data JPA repository code through the @DataJpaTest annotation
  • JSON slice: JSON serialization code through the @JsonTest annotation

This may not seem like much at first glance. But when you have a large application starting the Spring context in testing, it is time consuming. Context loads can really increase your build time.

Let’s start putting the new test features to use.

The Application Under Test

I wrote a series of posts on Spring MVC starting off from Spring Boot Web Application – Part 1 – Spring Initializer. In the last post of the series, Spring Boot Web Application – Part 4 – Spring MVC, I completed creating a Spring MVC application to perform Create, Read, Update, and Delete (CRUD) operations.

In this post, I’ll show you how to write tests for the controllers of the same Spring MVC application.

If you are new to Spring MVC, you should go through my series on Spring MVC starting here.

You can also download the source code of the application available on GitHub here to follow along this post.

It’s a pretty simple example of a Spring Boot MVC application consisting of the following primary components:

  • Product: The domain object, which is a JPA entity
  • IndexController: Returns the index.html Thymeleaf template for a GET request to the application root
  • ProductController: Contains number of actions methods that use ProductService to perform CRUD operations via the repository model
  • ProductRepository: A Spring Data JPA repository
  • ProductService: A business service façade interface
  • ProductServiceImpl: A business service façade implementation annotated with @Service

With the Spring Boot MVC application that will be under test in place, lets start by writing few tests for the controllers.

Maven Dependencies

The testing features we’re looking at were introduced in Spring Boot 1.4. The version of Spring Boot we’ll be using is 1.4.0.RELEASE.

Here is the complete Maven POM that we’ll use.

pom.xml

Unit Testing Spring MVC Controllers

MockMvc has been around since Spring 3.2. This providing a powerful way to mock Spring MVC for testing MVC web applications. Through MockMvc, you can send mock HTTP requests to a controller and test how the controller behaves without running the controller within a server. You can obtain a MockMvc instance through the following two methods of MockMvcBuilders:

  • standaloneSetup(): Registers one or more @Controller instances and allows programmatically configuring the Spring MVC infrastructure to build a MockMvc instance. This is similar to plain unit tests while also making it possible to focus tests around a single controller at a time.
  • webAppContextSetup(): Uses the fully initialized (refreshed) WebApplicationContext to build a MockMvc instance. This lets Spring load your controllers as well as their dependencies for a full-blown integration test.

Pro Tip: Whenever possible, I will try to use standaloneSetup() for my SpringMVC tests. Your tests will remain true unit tests and stay blazing fast!

This is the IndexController that we are going to test:

IndexController.java

For our purpose, we’re starting with standaloneSetup() to test this IndexController.

The test class is this.

IndexControllerTest.java

The test class above is a JUnit test. If you are new to JUnit, you should go through my series on unit testing with JUnit, starting from here. In the test class above, observe the new Spring Boot 1.4 test runner, named SpringRunner that we specified for @RunWith in Line 20. Under the hood, both SpringRunner and its predecessor SpringJUnit4ClassRunner are the same. SpringRunner is only the new name for SpringJUnit4ClassRunner – to just make it easy on the eyes.
In the @Before annotated method that runs before all @Test method, we programmatically constructed a MockMvc instance after registering the IndexController instance.

In the @Test method, we used the MockMvc instance to verify the following behavior of IndexController:

  • HTTP status code 200 is returned
  • The name of the returned view is index

Finally, by using andDo(print()), we get the following output on the console

Spring MockMvc IndexController Test Output

Testing the Spring MVC Slice

The unit test we wrote were for some basic expectations of the controller. Let’s write some more specific tests, this time to test ProductController. This time we’re going to use webAppContextSetup() to build MockMvc.

For a quick recap, the ProductController class is this.

ProductController.java

We will start by testing the behavior of ProductController.list() method. For a GET request to /product, we will perform the following verification:

  • The ProductService mock is not null
  • The HTTP status code 200 is returned
  • The returned content type is text/html;charset=UTF-8
  • The name of the returned view is products
  • The view contains the Spring Framework Guru string

Here is the test class.

ProductControllerTest.java

As we are testing the MVC slice of the application (testing whether the ProductController is working as expected), we used the @WebMvcTest annotation combined with @RunWith(SpringRunner.class).

As we planned to use webAppContextSetup() to build MockMvc, we @autowired WebApplicationContext in Line 6 – Line 7 to bring it into our test. Then in line 13, we passed WebApplicationContext as an argument to webAppContextSetup() to build the MockMvc instance.

Going back to the ProductController class under test, note that the controller class is @Autowired with ProductService. Therefore, we used the @MockBean annotation to define a Mockito mock for ProductService (Line 8 -Line 9) that will be passed to the controller. If you are new to mocking in unit tests, checkout my Mocking in Unit Tests with Mockito post.

Coming back to the test, in Line 17 we used the AssertJ library to assert that the ProductService mock is not null.

Note: Starting with Spring Boot 1.4, AssertJ comes out-of-the-box with Spring Boot to provide a fluent assertion API with a plan to replace JUnit’s  org.junit.Assert class.

From Line 19 – Line 23, it’s all about verifying our expectations. As you can see, a lot of static methods are being used in this test method, including static methods of MockMvcRequestBuilders ( get()), MockMvcResultMatchers ( status(), content(), and view()), MockMvcResultMatchers ( match()), and Hamcrest Matcher’s ( match()). The last two match() are similar and performs the same functions in our test. They exist together only to demonstrate the different approaches that can be used.

Our test method reads naturally. First it performs a

First it performs a GET request against /products. Then it expects that the request is successful ( isOk() asserts an HTTP 200 response code) and that the content type and name of the view is text/html;charset=UTF-8 and products respectively. Finally, it asserts that the view contains the Spring Framework Guru string.

When all the expectations pass, Line 24 prints the result out to the console.

The important thing to note here is that at no time is the application gets deployed to a server. The Tomcat container is not use. Instead the application runs within a mocked out Spring MVC to handle the HTTP request that we provided through the

Instead the application runs within a mocked out Spring MVC to handle the HTTP request that we provided through the MockMvc instance.

Here is the test result in the console.
Test output of ProductController.list()

The complete output of the test sent to console is this.

Testing Spring MVC Slice with @Autowired MockMvc

Now let’s test the behavior of showProduct() of ProductController. Instead of manually building MockMvc, we’ll use a @Autowired MockMvc in the test and let Spring create, configure, and provide a MockMvc for us.

This is how the test class now looks minus any @Test method implementations.

In the test class above, notice that we used the @Autowired annotation on MockMvc in Line 5 – Line 6 instead of building it manually.

An @Autowired MockMvc combined with @WebMvcTest(controllers = ProductController.class) gives us a fully configured MockMvc instance with Spring security configured to set up BASIC authentication.

At this point, if we run the ProductControllerTest.testList() test again, we’ll encounter an authentication error, like this.

We’re getting the 401 response because Spring Boot is auto-configuring Spring Security for us.

To disable the Spring Security auto-configuration, we can the MockMvc instance to disable security with @AutoConfigureMockMvc(secure=false) in Line 3.

Note, in the @Before method, we created and initialized a Product domain object that we will use in the @Test method.

The @Test method is this:

In the @Test method above:

  • Line 4: Performs an AssertJ assertion to test that the ProductService mock is not null.
  • Line 5: Uses Mockito to stub the getProductById() method on the ProductService mock to return the initialized Product instance
  • Line 8 to Line 15: Performs the following verifications for a GET request to product/{id}:
    • The HTTP status code 200 is returned
    • The name of the returned view is productshow
    • The view model contains a product attribute
    • The various properties of the product attribute matches against the values we used to initialize Product
  • Line 16: Returns the result as MvcResult
  • Line 19- Line 20: Uses AssertJ to assert that the content type of the response is
    text/html;charset=UTF-8
  • Line 22- Line 27: Uses JUnit assertions to assert that:
    • The response header that MvcResult returns as MockHttpServletResponse is not null
    • There is only one response header
    • The response header name is Content-Type
    • The response contains the Spring Framework Guru string
  • Line 29 -Line 30: Uses Mockito to verify that the getProductById() is called only once on the ProductService mock, and that no other methods of the ProductService mock are called during the test.

The complete test class is this:

ProductControllerTest.java

The complete output of the test sent to console is this:

Summary

The new @WebMVC used with MockBean allows creating powerful yet simple tests for your Spring MVC apps. Unlike the @SpringBootTest annotation, the @WebMvcTest annotation disables full auto-configuration. @WebMvcTest only auto-configures the Spring MVC infrastructure and limits scanned beans to @Controller, @ControllerAdvice, @JsonComponent, Filter, WebMvcConfigurer, and HandlerMethodArgumentResolver beans.

When you use @WebMvcTest, regular @Component, @Service, or @Repository beans will not be scanned – an important point to differentiate @WebMvcTest from a full-blown @SpringBootTest.

If you’re looking to load your full application configuration and use MockMVC, you should consider @SpringBootTest combined with @AutoConfigureMockMvc rather than @WebMvcTest. I will cover it in an upcoming post of this Spring MVC testing series. I will also help you to explore more about mocking services and JPA repositories with @MockBean combined with @DataJpaTest and @WebMvcTest, and also how to unit test RESTful controller’s GETs and POSTs using MockMvc and @JsonTest.

7
Share