«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, открывающий наш домен 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
}
@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);
}
}
И, что не менее важно, , наш класс обслуживания и @ControllerAdvice для обработки нашего CourseNotFoundException:
Теперь, когда у нас есть система для тестирования, давайте взглянем на несколько тестов RestAssuredMockMvc.
5. Модульное тестирование контроллера REST с поддержкой REST
@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 с нашими любимыми инструментами тестирования, JUnit и Mockito, для тестирования нашего @RestController.
Сначала мы имитируем и создаем нашу SUT, а затем инициализируем RestAssuredMockMvc в автономном режиме, как указано выше:
Поскольку мы инициализировали 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("[]")));
}
Автономный режим отлично подходит для модульных тестов, поскольку он инициализирует только предоставленные нами контроллеры, а не весь контекст приложения. Это делает наши тесты быстрыми.
@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());
}
Теперь давайте посмотрим на пример теста:
-
Инициализация RestAssuredMockMvc с нашим @ControllerAdvice в дополнение к нашему @RestController также позволяет нам тестировать наши сценарии исключений:
Как показано выше, REST- sure использует знакомый формат сценария «данно-когда-потом» для определения теста: ” проверяет ответ HTTP
6. Тестирование интеграции контроллера REST с поддержкой REST
Мы также можем использовать RestAssuredMockMvc с инструментами тестирования Spring для наших интеграционных тестов.
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
public class CourseControllerIntegrationTest {
//...
}
Во-первых, мы настраиваем наш тестовый класс с помощью @RunWith(SpringRunner.class) и @SpringBootTest(webEnvironment = RANDOM_PORT):
Это запустит наш тест с контекстом приложения, настроенным в нашем классе @SpringBootApplication на случайный порт.
@Autowired
private WebApplicationContext webApplicationContext;
@Before
public void initialiseRestAssuredMockMvcWebApplicationContext() {
RestAssuredMockMvc.webAppContextSetup(webApplicationContext);
}
Затем мы внедряем наш WebApplicationContext и используем его для инициализации RestAssuredMockMvc, как указано выше:
@Test
public void givenNoMatchingCourseCodeWhenGetCourseThenRespondWithStatusNotFound() {
String nonMatchingCourseCode = "nonMatchingCourseCode";
given()
.when()
.get("/courses/" + nonMatchingCourseCode)
.then()
.log().ifValidationFails()
.statusCode(NOT_FOUND.value());
}
Теперь, когда у нас настроен тестовый класс и инициализирован RestAssuredMockMvc, мы готовы начать писать наши тесты:
Помните, поскольку мы инициализировали RestAssuredMockMvc статически в нашем методе @Before, нет необходимости инициализировать его в каждом тесте.
Для более глубокого изучения REST-assured API ознакомьтесь с нашим REST-assured Guide.
7. Заключение
«В этом руководстве мы увидели, как мы можем использовать REST-assured для тестирования нашего приложения Spring MVC с использованием модуля spring-mock-mvc REST-assured.
Инициализация RestAssuredMockMvc в автономном режиме отлично подходит для модульного тестирования, поскольку она инициализирует только предоставленные контроллеры, что ускоряет наши тесты.
Инициализация RestAssuredMockMvc в режиме контекста веб-приложения отлично подходит для интеграционного тестирования, поскольку он использует наш полный WebApplicationContext.