REST-assured Support for Spring MockMvc – 对Spring MockMvc的REST保证支持

最后修改: 2019年 4月 2日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

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.

在本教程中,我们将学习如何使用RestAssuredMockMvc测试我们的Spring REST控制器,这是一个建立在Spring的MockMvc之上的REST保证API。

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.

本教程使用了Spring MVCSpring MockMVCREST-assured,因此请务必也查看这些教程。

2. Maven Dependency

2.Maven的依赖性

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:

在开始编写测试之前,我们需要将io.rest-assured:spring-mock-mvc模块导入我们的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

3.初始化RestAssuredMockMvc

Next up, we need to initialize RestAssuredMockMvc, the starting point of the DSL, in either standalone or web application context mode.

接下来,我们需要初始化RestAssuredMockMvc,DSL的起点,在独立web应用程序上下文模式下。

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

3.1 独立的

In standalone mode, we initialize RestAssuredMockMvc with one or more @Controller or @ControllerAdvice annotated classes.

在独立模式下,我们用一个或多个@Controller@ControllerAdvice注释的类来初始化RestAssuredMockMvc

If we only have a few tests, we can initialize RestAssuredMockMvc just in time:

如果我们只有几个测试,我们可以及时初始化RestAssuredMockMvc

@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

3.2.网络应用程序语境

In web application context mode, we initialize RestAssuredMockMvc with an instance of a Spring WebApplicationContext.

在Web应用上下文模式下,我们用Spring WebApplicationContext的一个实例初始化RestAssuredMockMvc

Similar to what we saw in the standalone mode setup, we can initialize RestAssuredMockMvc just in time on each test:

与我们在独立模式设置中看到的情况类似,我们可以在每个测试中及时初始化RestAssuredMockMvc

@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)

4. 被测系统(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配置开始。

@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,暴露我们的Course域。

@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:

最后,我们的服务类和@ControllerAdvice来处理我们的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.

现在我们有一个系统要测试,让我们看看几个RestAssuredMockMvc测试。

5. REST Controller Unit Testing with REST-assured

5.用REST-assured进行REST控制器单元测试

We can use RestAssuredMockMvc with our favorite test tools, JUnit and Mockito, to test our @RestController.

我们可以将RestAssuredMockMvc与我们喜爱的测试工具JUnitMockito一起使用,来测试我们的@RestController

First, we mock and construct our SUT and then initialize RestAssuredMockMvc in standalone mode as above:

首先,我们模拟并构建我们的SUT,然后如上所述以独立模式初始化RestAssuredMockMvc

@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.

因为我们已经在@Before方法中静态地初始化了RestAssuredMockMvc,所以没有必要在每个测试中初始化它。

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.

Standalone模式对于单元测试来说非常好,因为它只初始化我们提供的控制器,而不是整个应用程序上下文。这使我们的测试保持快速。

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:

除了我们的@RestControllerAdvice之外,用我们的@RestController初始化RestAssuredMockMvc也使我们能够测试我们的异常情景。

@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:

如上所述,REST-assured使用熟悉的给定-何时-何时场景格式来定义测试。

  • 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

6.使用REST-assured进行REST控制器集成测试

We can also use RestAssuredMockMvc with Spring’s test tools for our integration tests.

我们还可以将RestAssuredMockMvc与Spring的测试工具一起用于集成测试。

First, we set up our test class with @RunWith(SpringRunner.class) and @SpringBootTest(webEnvironment = RANDOM_PORT):

首先,我们用@RunWith(SpringRunner.class)@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.

这将在我们的@SpringBootApplication类中配置的应用程序上下文中运行我们的测试,并在一个随机端口上运行。

Next, we inject our WebApplicationContext and use it to initialize RestAssuredMockMvc as above:

接下来,我们注入我们的WebApplicationContext并使用它来初始化RestAssuredMockMvc,如上所述。

@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:

现在我们已经设置了我们的测试类并初始化了RestAssuredMockMvc,我们准备开始编写我们的测试。

@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.

记住,由于我们已经在@Before方法中静态地初始化了RestAssuredMockMvc,所以没有必要在每个测试中初始化它。

For a deeper dive into the REST-assured API, check out our REST-assured Guide.

要深入了解REST-assured API,请查看我们的REST-assured指南

7. Conclusion

7.结语

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.

在本教程中,我们看到如何使用REST-assured的spring-mock-mvc模块来测试我们的Spring MVC应用。

Initializing RestAssuredMockMvc in standalone mode is great for unit testing since it only initializes the provided Controllers, keeping our tests fast.

独立模式下初始化RestAssuredMockMvc对于单元测试来说是非常好的,因为它只初始化所提供的Controllers,保持我们的测试速度。

Initializing RestAssuredMockMvc in web application context mode is great for integration testing since it uses our complete WebApplicationContext.

Web应用程序上下文模式下初始化RestAssuredMockMvc对于集成测试来说是非常好的,因为它使用我们完整的WebApplicationContext

As always, you can find all of our sample code over on Github.

一如既往,你可以在Github上找到我们所有的示例代码