1. Введение
В этом руководстве мы узнаем, как тестировать наши Spring REST-контроллеры с помощью RestAssuredMockMvc
, API-интерфейса с поддержкой REST, созданного поверх Spring MockMvc
.
Во-первых, мы рассмотрим различные варианты установки. Затем мы углубимся в то, как писать как модульные, так и интеграционные тесты.
В этом руководстве используются 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, либо в автономном
режиме, либо в режиме контекста веб-приложения
.
В обоих режимах мы можем делать это либо вовремя для каждого теста, либо один раз статически. Давайте посмотрим на некоторые примеры.
3.1. Автономный
В автономном режиме мы инициализируем RestAssuredMockMvc
одним или несколькими аннотированными классами @Controller
или @ControllerAdvice
.
Если у нас есть только несколько тестов, мы можем вовремя инициализировать RestAssuredMockMvc
:
@Test
public void whenGetCourse() {
given()
.standaloneSetup(new CourseController())
//...
}
Но если у нас много тестов, будет проще сделать это один раз статически:
@Before
public void initialiseRestAssuredMockMvcStandalone() {
RestAssuredMockMvc.standaloneSetup(new CourseController());
}
3.2. Контекст веб-приложения
В режиме контекста веб-приложения мы инициализируем RestAssuredMockMvc
экземпляром Spring WebApplicationContext
.
Подобно тому, что мы видели в настройке автономного режима, мы можем инициализировать RestAssuredMockMvc
как раз вовремя для каждого теста:
@Autowired
private WebApplicationContext webApplicationContext;
@Test
public void whenGetCourse() {
given()
.webAppContextSetup(webApplicationContext)
//...
}
Или, опять же, мы можем просто сделать это один раз статически:
@Autowired
private WebApplicationContext webApplicationContext;
@Before
public void initialiseRestAssuredMockMvcWebApplicationContext() {
RestAssuredMockMvc.webAppContextSetup(webApplicationContext);
}
4. Тестируемая система (SUT)
Прежде чем мы погрузимся в несколько примеров тестов, нам нужно кое-что протестировать. Давайте проверим нашу тестируемую систему, начиная с нашей конфигурации @SpringBootApplication
:
@SpringBootApplication
class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Далее у нас есть простой @RestController
, открывающий наш домен курса
:
@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
Мы можем использовать RestAssuredMockMvc
с нашими любимыми инструментами тестирования, JUnit и Mockito , чтобы протестировать наш @RestController
.
Сначала мы имитируем и создаем нашу 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);
}
Поскольку мы инициализировали RestAssuredMockMvc
статически в нашем методе @Before
, нет необходимости инициализировать его в каждом тесте.
Автономный режим отлично подходит для модульных тестов, поскольку он инициализирует только предоставленные нами контроллеры , а не весь контекст приложения. Это делает наши тесты быстрыми.
Теперь давайте посмотрим пример теста:
@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("[]")));
}
Инициализация RestAssuredMockMvc
с нашим @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 использует знакомый формат сценария «данно-когда-тогда» для определения теста:
заданный ()
— указывает детали HTTP-запросаwhen()
— указывает глагол HTTP, а также маршрутthen()
— проверяет HTTP-ответ.
6. Тестирование интеграции контроллера REST с поддержкой REST
Мы также можем использовать 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());
}
Помните, поскольку мы инициализировали RestAssuredMockMvc
статически в нашем методе @Before
, нет необходимости инициализировать его в каждом тесте.
Для более глубокого изучения REST-assured API ознакомьтесь с нашим руководством по REST-assured .
7. Заключение
В этом руководстве мы увидели, как мы можем использовать REST-assured для тестирования нашего приложения Spring MVC с использованием модуля spring-mock-mvc REST-assured.
Инициализация RestAssuredMockMvc
в автономном режиме отлично подходит для модульного тестирования , поскольку она инициализирует только предоставленные контроллеры
, что ускоряет наши тесты.
Инициализация RestAssuredMockMvc
в режиме контекста веб-приложения отлично подходит для интеграционного тестирования , поскольку он использует наш полный WebApplicationContext
.
Как всегда, вы можете найти весь наш пример кода на Github .