REST Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

If you have a few years of experience in the Java ecosystem, and you're interested in sharing that experience with the community (and getting paid for your work of course), have a look at the "Write for Us" page. Cheers. Eugen

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.

REST bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE