Перейти к основному содержимому

Гарантированная поддержка REST для Spring MockMvc

· 5 мин. чтения

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 .