1. 简介

在本教程中,我们将学习如何使用 RestAssuredMockMvc 对 Spring 的 REST 控制器进行测试。RestAssured 是一个基于 Java 的测试框架,其提供的 spring-mock-mvc 模块可以非常方便地测试 Spring MVC 应用中的 Controller 层。

我们会介绍两种测试模式:Standalone 模式WebApplicationContext 模式,分别适用于单元测试和集成测试。

2. Maven 依赖

在开始写测试之前,我们需要在 pom.xml 中引入 spring-mock-mvc 模块:

<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>spring-mock-mvc</artifactId>
    <version>3.3.0</version>
    <scope>test</scope>
</dependency>

✅ 确保版本号与你使用的 REST-assured 主版本一致。

3. 初始化 RestAssuredMockMvc

初始化 RestAssuredMockMvc 是使用 REST-assured 的起点。支持两种初始化方式:

3.1 Standalone 模式

适用于单元测试,手动传入需要测试的 @Controller@ControllerAdvice 实例。

单次初始化(推荐)

@Before
public void initialiseRestAssuredMockMvcStandalone() {
    RestAssuredMockMvc.standaloneSetup(new CourseController());
}

每次测试前初始化(不推荐)

@Test
public void whenGetCourse() {
    given()
      .standaloneSetup(new CourseController())
      //...
}

⚠️ 适合测试少量接口时使用,频繁初始化影响性能。

3.2 WebApplicationContext 模式

适用于集成测试,使用 Spring 的 WebApplicationContext 初始化。

静态初始化(推荐)

@Autowired
private WebApplicationContext webApplicationContext;

@Before
public void initialiseRestAssuredMockMvcWebApplicationContext() {
    RestAssuredMockMvc.webAppContextSetup(webApplicationContext);
}

每次测试初始化(不推荐)

@Test
public void whenGetCourse() {
    given()
      .webAppContextSetup(webApplicationContext)
      //...
}

✅ 推荐使用静态初始化,避免重复操作。

4. 被测系统(SUT)

我们以一个典型的 Spring Boot 项目为例,包含一个 REST 接口 CourseController

@SpringBootApplication
class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
@RestController
@RequestMapping("/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("/{code}")
    public Course getCourse(@PathVariable String code) {
        return courseService.getCourse(code);
    }
}
class Course {
    private String code;
    // 构造方法、getter、setter 等
}

服务层与异常处理:

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

5. 使用 REST-assured 进行单元测试

我们使用 JUnit + Mockito 来进行单元测试,结合 RestAssuredMockMvc 的 standalone 模式:

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

✅ 使用 standalone 模式,只加载指定的 Controller,测试速度快。

示例测试:获取课程列表

@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("[]")));
}

示例测试:获取不存在的课程返回 404

@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());
}

✅ 使用 given-when-then 风格,结构清晰,可读性强。

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

对于集成测试,我们使用完整的 Spring 上下文:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CourseControllerIntegrationTest {

    @Autowired
    private WebApplicationContext webApplicationContext;

    @Before
    public void initialiseRestAssuredMockMvcWebApplicationContext() {
        RestAssuredMockMvc.webAppContextSetup(webApplicationContext);
    }

示例测试:获取不存在的课程返回 404

@Test
public void givenNoMatchingCourseCodeWhenGetCourseThenRespondWithStatusNotFound() {
    String nonMatchingCourseCode = "nonMatchingCourseCode";

    given()
      .when()
        .get("/courses/" + nonMatchingCourseCode)
      .then()
        .log().ifValidationFails()
        .statusCode(HttpStatus.NOT_FOUND.value());
}

✅ 使用 WebApplicationContext 模式,测试更贴近真实运行环境。

7. 小结

模式 适用场景 特点
Standalone 模式 单元测试 只加载指定 Controller,速度快
WebApplicationContext 模式 集成测试 加载完整上下文,更真实

REST-assured 提供了 DSL 风格的 API,非常适合测试 RESTful 接口。对于 Spring MVC 项目,使用 RestAssuredMockMvc 可以非常方便地进行接口测试,且测试代码简洁易读。

完整代码示例请参考:GitHub 仓库


原始标题:REST-assured Support for Spring MockMvc | Baeldung