1. Introduction
In this tutorial, we’re going to learn how to test our Spring REST Controllers using RestAssuredMockMvc, a REST-assured API built on top of Spring’s MockMvc.
First, we’ll examine the different setup options. Then, we’ll dive into how to write both unit and integration tests.
This tutorial uses Spring MVC, Spring MockMVC, and REST-assured, so be sure to check out those tutorials, too.
2. Maven Dependency
Before we get started writing our tests, we’ll need to import the io.rest-assured:spring-mock-mvc module into our Maven pom.xml:
<dependency> <groupId>io.rest-assured</groupId> <artifactId>spring-mock-mvc</artifactId> <version>3.3.0</version> <scope>test</scope> </dependency>
3. Initializing RestAssuredMockMvc
Next up, we need to initialize RestAssuredMockMvc, the starting point of the DSL, in either standalone or web application context mode.
In both modes, we can either do this just-in-time per test or once statically. Let’s take a look at some examples.
3.1. Standalone
In standalone mode, we initialize RestAssuredMockMvc with one or more @Controller or @ControllerAdvice annotated classes.
If we only have a few tests, we can initialize RestAssuredMockMvc just in time:
@Test public void whenGetCourse() { given() .standaloneSetup(new CourseController()) //... }
But, if we have a lot of tests, it’s going to be easier to just do it once statically:
@Before public void initialiseRestAssuredMockMvcStandalone() { RestAssuredMockMvc.standaloneSetup(new CourseController()); }
3.2. Web Application Context
In web application context mode, we initialize RestAssuredMockMvc with an instance of a Spring WebApplicationContext.
Similar to what we saw in the standalone mode setup, we can initialize RestAssuredMockMvc just in time on each test:
@Autowired private WebApplicationContext webApplicationContext; @Test public void whenGetCourse() { given() .webAppContextSetup(webApplicationContext) //... }
Or, again, we can just do it once statically:
@Autowired private WebApplicationContext webApplicationContext; @Before public void initialiseRestAssuredMockMvcWebApplicationContext() { RestAssuredMockMvc.webAppContextSetup(webApplicationContext); }
4. System Under Test (SUT)
Before we dive into a few example tests, we’re going to need something to test. Let’s check out our system under test, starting with our @SpringBootApplication configuration:
@SpringBootApplication class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Next up, we have a simple @RestController exposing our Course domain:
@RestController @RequestMapping(path = "/courses") public class CourseController { private final CourseService courseService; public CourseController(CourseService courseService) { this.courseService = courseService; } @GetMapping(produces = APPLICATION_JSON_UTF8_VALUE) public Collection<Course> getCourses() { return courseService.getCourses(); } @GetMapping(path = "/{code}", produces = APPLICATION_JSON_UTF8_VALUE) public Course getCourse(@PathVariable String code) { return courseService.getCourse(code); } }
class Course { private String code; // usual contructors, getters and setters }
And, last but not least, our service class and @ControllerAdvice to handle our CourseNotFoundException:
@Service class CourseService { private static final Map<String, Course> COURSE_MAP = new ConcurrentHashMap<>(); static { Course wizardry = new Course("Wizardry"); COURSE_MAP.put(wizardry.getCode(), wizardry); } Collection<Course> getCourses() { return COURSE_MAP.values(); } Course getCourse(String code) { return Optional.ofNullable(COURSE_MAP.get(code)).orElseThrow(() -> new CourseNotFoundException(code)); } }
@ControllerAdvice(assignableTypes = CourseController.class) public class CourseControllerExceptionHandler extends ResponseEntityExceptionHandler { @ResponseStatus(HttpStatus.NOT_FOUND) @ExceptionHandler(CourseNotFoundException.class) public void handleCourseNotFoundException(CourseNotFoundException cnfe) { //... } }
class CourseNotFoundException extends RuntimeException { CourseNotFoundException(String code) { super(code); } }
Now that we have a system to test, let’s have a look at a few RestAssuredMockMvc tests.
5. REST Controller Unit Testing with REST-assured
We can use RestAssuredMockMvc with our favorite test tools, JUnit and Mockito, to test our @RestController.
First, we mock and construct our SUT and then initialize RestAssuredMockMvc in standalone mode as above:
@RunWith(MockitoJUnitRunner.class) public class CourseControllerUnitTest { @Mock private CourseService courseService; @InjectMocks private CourseController courseController; @InjectMocks private CourseControllerExceptionHandler courseControllerExceptionHandler; @Before public void initialiseRestAssuredMockMvcStandalone() { RestAssuredMockMvc.standaloneSetup(courseController, courseControllerExceptionHandler); }
Because we’ve initialized RestAssuredMockMvc statically in our @Before method, there’s no need to initialize it in each test.
Standalone mode is great for unit tests because it only initializes the controllers that we provide, rather than the entire application context. This keeps our tests fast.
Now, let’s see an example test:
@Test public void givenNoExistingCoursesWhenGetCoursesThenRespondWithStatusOkAndEmptyArray() { when(courseService.getCourses()).thenReturn(Collections.emptyList()); given() .when() .get("/courses") .then() .log().ifValidationFails() .statusCode(OK.value()) .contentType(JSON) .body(is(equalTo("[]"))); }
Initializing RestAssuredMockMvc with our @ControllerAdvice in addition to our @RestController enables us to test our exception scenarios too:
@Test public void givenNoMatchingCoursesWhenGetCoursesThenRespondWithStatusNotFound() { String nonMatchingCourseCode = "nonMatchingCourseCode"; when(courseService.getCourse(nonMatchingCourseCode)).thenThrow(new CourseNotFoundException(nonMatchingCourseCode)); given() .when() .get("/courses/" + nonMatchingCourseCode) .then() .log().ifValidationFails() .statusCode(NOT_FOUND.value()); }
As seen above, REST-assured uses the familiar given-when-then scenario format to define the test:
- given() — specifies the HTTP request details
- when() — specifies the HTTP verb as well as the route
- then() — validates the HTTP response
6. REST Controller Integration Testing with REST-assured
We can also use RestAssuredMockMvc with Spring’s test tools for our integration tests.
First, we set up our test class with @RunWith(SpringRunner.class) and @SpringBootTest(webEnvironment = RANDOM_PORT):
@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = RANDOM_PORT) public class CourseControllerIntegrationTest { //... }
This will run our test with the application context configured in our @SpringBootApplication class on a random port.
Next, we inject our WebApplicationContext and use it to initialize RestAssuredMockMvc as above:
@Autowired private WebApplicationContext webApplicationContext; @Before public void initialiseRestAssuredMockMvcWebApplicationContext() { RestAssuredMockMvc.webAppContextSetup(webApplicationContext); }
Now that we have our test class set up and RestAssuredMockMvc initialized, we’re ready to start writing our tests:
@Test public void givenNoMatchingCourseCodeWhenGetCourseThenRespondWithStatusNotFound() { String nonMatchingCourseCode = "nonMatchingCourseCode"; given() .when() .get("/courses/" + nonMatchingCourseCode) .then() .log().ifValidationFails() .statusCode(NOT_FOUND.value()); }
Remember, since we have initialized RestAssuredMockMvc statically in our @Before method, there’s no need to initialize it in each test.
For a deeper dive into the REST-assured API, check out our REST-assured Guide.
7. Conclusion
In this tutorial, we saw how we can use REST-assured to test our Spring MVC application using REST-assured’s spring-mock-mvc module.
Initializing RestAssuredMockMvc in standalone mode is great for unit testing since it only initializes the provided Controllers, keeping our tests fast.
Initializing RestAssuredMockMvc in web application context mode is great for integration testing since it uses our complete WebApplicationContext.
As always, you can find all of our sample code over on Github.