«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.