Testing Spring Boot RESTful APIs using MockMvc/Mockito, Test RestTemplate and RestAssured

Jetzel Cabral
The Startup
Published in
6 min readDec 1, 2019

--

This project serves as a Spring Boot testing demo for a simple web app. The post covers testing the same web app API using 3 different methods:

  1. Web layer Unit testing using MockMvc and Mockito
  2. Integration testing using RestAssured library
  3. Integration testing using Spring’s TestRestTemplate

If you don’t care about the “what, why, how” and just want the code examples you can skip this introduction.

Here is the complete GitHub project source code:

Environment, Tools and Dependencies

Table showing project dependencies, env and tools
build.gradle

Application Overview

Got Swag? — Yes (Displays all URI endpoints in the service)
App Flow

At a high level, the app works with a database containing vehicle records and exposes these records via URI endpoints. The app allows your typical HTTP requests (GET, PUT, POST, DELETE) for manipulating and retrieving the records. Before a client is ready to use our web app vehicle service we want to ensure that our endpoints will work as intended.

We’ll go over a 2 types of tests- web layer tests which primarily focus on testing the web app’s controller layers and integration tests which test the whole web application end to end.

Code gists and project structure overview

Vehicle DTO:

For our Vehicle class we’ll use Lombok which helps us avoid writing a lot of boilerplate code like getters, setters, constructors, builders, equals override, etc. The vehicle entity objects will be mapped to the ‘vehicles’ table in the H2 database.

Vehicle Controller:

Exposes our RESTful API to clients via HTTP requests and responses.

Service implementation:

Handles business logic. For example, our service has to account for special cases when dealing with older vehicle VIN standards.

Repository Interface:

No more DAO implementations! The repository interface uses Spring’s dark magic yielding implementations to the most relevant CRUD methods. Just kidding there’s no dark magic[sighs], Spring internally uses naming conventions and reflection to create query implementations for methods like ‘findBy[FieldName]’.

data-h2.sql

H2 Database Schema:

When performing integration tests, we really shouldn’t be using production databases. Setting up a test database that multiple developers use concurrently might also not be the best option. Instead, we’ll use an in-memory relational database (H2 DB) for this demo. All we need to do is two things:

  1. Include the ‘h2database’ run-time dependency in your maven or gradle file
  2. Add the database configurations like the DB URL, driver, and jpa platform to the application-{}.properties file.

That’s essentially it, Spring will auto-configure the rest upon starting the application. It will start the database and terminate it whenever the web app starts and ends respectively; hence why it’s an “in-memory” DB. Spring will execute the data-h2.sql file when running our demo app.

Web Layer Tests (What? Why? How?)

Web layer testing is essentially writing fine-grained tests specifically designed to test your app’s controllers. It is very similar to writing regular unit tests for classes where you need mock dependencies for testing certain methods.

A couple of reasons for creating web layer tests include not having service, repository, database or any other dependencies completed in your project and needing to test your controller classes. These tests will allow you to validate the controller properly handles respective HTTP requests. Since these are practically unit tests, you can additionally include them as part of your apps’ code coverage percentage with tools like JaCoCo. On a side note, these tests will likely run faster than integration tests since they won’t require the web server to run on.

A web layer test using the @WebMvcTest annotation starts a Spring Application Context. A web layer test will not start the default embedded tomcat web server spring boot that it comes with. @WebMvcTest will solely focus on loading only the designated controller class dependencies. @WebMvcTest will NOT load all auto-configurations such as any @Component, @Service or @Repository beans. However, it does include some auto configurations such as Spring security, @AutoConfigureWebMvc, @AutoConfigureMockMvc, @Controller, @ControllerAdvice among other functionalities. Because the @Service bean is excluded, it will be necessary to mock the VehicleServiceImpl class in our test example. We will use Mockito to assist us with that.

Speaking of @AutoConfigureMockMvc, we will use MockMvc- Spring’s MVC test framework- to perform HTTP requests on web endpoints inside our mock web environment. Since the MockMvc bean is loaded into the context, we will be able to simply @Autowire it for our use.

Here’s the web layer unit testing using MockMvc and Mockito example:

DemoWebLayerTest.java

Integration Tests (What? Why? How?)

Integration testing allows us to test all of our application components at once to validate they function adequately. We’ll send HTTP requests to our controller, which at its time will forward the requests to our service implementation where any additional business logic will be handled. The requests will then reach our CRUD repository allowing us to fetch or manipulate whatever data is in our in-memory DB. Finally, the response will be returned to the client.

These tests are essentially used to imitate real requests our API will receive from clients. The tests will allow us to validate our expected client response payload and status codes for the respective client requests. These tests tend to take longer executing because they spin up the default embedded tomcat web server and deploying our app on it.

@SpringBootTest annotation enables a testing application context, allowing us to execute methods annotated with @Test. By default, SpringBootTest will not start the server. But if we define the 'webEnvironment' property as @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT OR WebEnvironment.DEFINED_PORT), spring will load a WebServerApplicationContext providing a real web environment. This property will start the embedded server listening on the defined or random port. The context uses the SpringBootContextLoader class and the @SpringBootConfiguration annotation by default. For your tests, you may specify which component classes to use for loading the Application Context and override specific configurations.

So how will we communicate with our API to mimic the HTTP requests? Glad you asked! To accomplish this, we’ll use both Spring’s TestRestTemplate and the REST-Assured framework.

TestRestTemplate vs. REST-Assured

It’s like comparing Coke vs. Pepsi- they’re pretty much the same thing. Just kidding Pepsi sucks! But in all seriousness, when it comes to RESTful based API integration testing and validation, both do the same thing. TestRestTemplate and REST-Assured offer convenient methods to create and execute your HTTP calls with custom headers, auth, content types, query parameters, payload content, cookies, etc. The main difference -aside from syntax- is that TestRestTemplate is part of Spring’s test framework which comes bundled with the ‘spring-boot-starter-test’ dependency. When using @SpringBootTest, TestRestTemplate’s bean is registered in the application context by default.

In this example, we’ll use both libraries to test and validate the same vehicle service API so that you can get an idea on how they are implemented. Let’s take a look at the rest-assured integration tests first:

Integration testing using Rest-Assured library example

Rest-assured integration tests

Integration testing using Spring’s TestRestTemplate example

TestRestTemplate integration tests

Note: One caveat of web app integration tests, is we can’t use @Transactional to roll back database transactions at the end of each test method. This is because the HTTP client and server run in separate threads. Having said this, be cautious of your assertions- you don’t want to assert an object exists right after a delete request ran in the previous test. Additionally, you could drop and create the DB schema after every single test method, but if it’s a sizable schema, it might result in long execution time.

--

--