1. 簡介
在本教程中,我們將學習如何使用 RestAssuredMockMvc 測試我們的 Spring REST 控制器,這是一個建立在 Spring MockMvc 之上的 REST-assured API。
首先,我們將研究不同的設置選項。然後,我們將深入瞭解如何編寫單元測試和集成測試。
本教程使用 Spring MVC、Spring MockMVC 和 REST-assured,因此請務必查看這些教程。
2. Maven 依賴
在我們開始編寫測試之前,我們需要將 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. 初始 RestAssuredMockMvc
接下來,我們需要初始化 RestAssuredMockMvc,這是 DSL 的起點,可以是 standalone 或 web 應用程序上下文 模式。
在兩種模式下,我們可以隨時隨地(per test)進行初始化,也可以靜態地一次性進行。讓我們來看一些示例。
3.1. 獨立模式
在獨立模式下,我們 使用一個或多個 @Controller 或 @ControllerAdvice 類型的類來初始化 RestAssuredMockMvc。
如果只有幾個測試,我們可以隨時隨地(per test)初始化 RestAssuredMockMvc:
@Test
public void whenGetCourse() {
given()
.standaloneSetup(new CourseController())
//...
}
但是,如果有很多測試,那麼一次性靜態地進行會更容易:
@Before
public void initialiseRestAssuredMockMvcStandalone() {
RestAssuredMockMvc.standaloneSetup(new CourseController());
}
3.2. Web 應用程序上下文
在 web 應用程序上下文模式下,我們 使用 Spring 的 WebApplicationContext 實例來初始化 RestAssuredMockMvc。
類似於獨立模式的設置,我們可以在每個測試中隨時隨地(per test)初始化 RestAssuredMockMvc:
@Autowired
private WebApplicationContext webApplicationContext;
@Test
public void whenGetCourse() {
given()
.webAppContextSetup(webApplicationContext)
//...
}
或者,再次,我們可以一次性靜態地進行:
@Autowired
private WebApplicationContext webApplicationContext;
@Before
public void initialiseRestAssuredMockMvcWebApplicationContext() {
RestAssuredMockMvc.webAppContextSetup(webApplicationContext);
}
4. System Under Test (SUT)
在我們開始查看一些示例測試之前,我們需要一個測試對象。讓我們查看我們的系統下測試,從我們的 @SpringBootApplication 配置開始:
@SpringBootApplication
class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
接下來,我們有一個簡單的 @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
}
最後,我們的服務類和 @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);
}
}
現在我們有了測試對象,讓我們查看一些 RestAssuredMockMvc 測試。
5. REST 控制器單元測試與 REST-assured
我們可以使用 RestAssuredMockMvc 與我們最喜歡的測試工具 JUnit 和 Mockito 一起測試我們的 @RestController。
首先,我們 mock 並構造 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);
}
}
因為我們在 @Before 方法中靜態地初始化了 RestAssuredMockMvc,所以不需要在每個測試中初始化它。
獨立模式非常適合單元測試,因為它只初始化我們提供的控制器,而不是整個應用程序上下文。這使得我們的測試速度更快。
現在,讓我們看看一個示例測試:
@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("[]")));
}
將 @ControllerAdvice 附加到我們的 @RestController,使我們能夠測試我們的異常場景:
@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());
}
如上面所示,REST-assured 使用熟悉的 given-when-then 場景格式來定義測試:
- given() — 指定 HTTP 請求的詳細信息
- when() — 指定 HTTP 動詞以及路由
- then() — 驗證 HTTP 響應
6. REST 控制器集成測試與 REST-assured
我們還可以使用 RestAssuredMockMvc 與 Spring 的測試工具,用於我們的集成測試。
首先,我們設置測試類,使用 @RunWith(SpringRunner.class) 和 @SpringBootTest(webEnvironment = RANDOM_PORT):
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
public class CourseControllerIntegrationTest {
//...
}
這將使用在我們的 @SpringBootApplication 類中配置的應用程序上下文運行我們的測試,並在隨機端口上運行。
接下來,我們注入我們的 WebApplicationContext 並使用它來初始化 RestAssuredMockMvc,如上所述:
@Autowired
private WebApplicationContext webApplicationContext;
@Before
public void initialiseRestAssuredMockMvcWebApplicationContext() {
RestAssuredMockMvc.webAppContextSetup(webApplicationContext);
}
現在,我們已經設置了測試類並且 RestAssuredMockMvc 已經初始化,我們準備好開始編寫測試:
@Test
public void givenNoMatchingCourseCodeWhenGetCourseThenRespondWithStatusNotFound() {
String nonMatchingCourseCode = "nonMatchingCourseCode";
given()
.when()
.get("/courses/" + nonMatchingCourseCode)
.then()
.log().ifValidationFails()
.statusCode(NOT_FOUND.value());
}
請記住,由於我們在 @Before 方法中靜態地初始化了 RestAssuredMockMvc,因此我們不需要在每個測試中初始化它。
要更深入地瞭解 REST-assured API,請查看我們的 REST-assured 指南。
7. 結論
在本教程中,我們看到了如何使用 REST-assured 測試我們的 Spring MVC 應用程序,利用 REST-assured 的 spring-mock-mvc 模塊。
以 獨立模式 初始化 RestAssuredMockMvc 非常適合單元測試,因為它只初始化提供的 Controller,從而保持測試速度。
以 Web 應用程序上下文模式 初始化 RestAssuredMockMvc 非常適合集成測試,因為它使用我們的完整 WebApplicationContext。