Здравствуйте!
Собственный JSON Matcher, использование Spring Security Test, недавно вошедший в Spring Security 4.0, и отказ от транзакций при тестировании сервисов позволяют сделать тесты проще и надежнее.
Я использовал данный подход при создании приложения на своем курсе Topjava (Maven/ Spring/ Security/ JPA(Hibernate)/ Rest(Jackson)/ Bootstrap(CSS)/ jQuery + plugins), исходный код проекта можно взять на github.
Данная статья не является еще одним учебником по тестированию Spring REST контроллеров и предполагает что вы уже с ним знакомы.
Про выгоды отказа от транзакций я уже писал в предыдущей публикации По следам Spring Pet Clinic. Maven/ Spring Context/ Spring Test/ Spring ORM/ Spring Data JPA. К перечисленным недостаткам использования @Transaction еще можно добавить невозможность посмотреть реальные запросы при выполнении метода сервиса к базе (например при включенном hibernate.use_sql_comments)
Здесь я напишу как проще, короче, надежнее тестировать Spring REST контроллеры.
Spring Security Test
До релиза Spring Security 4.0 это был отдельный проект, который вошел в Spring Security 4.0 как Improved Testing Support.
Теперь он есть в центральном maven репозитории и подключается так:
<properties>
<spring-security.version>4.0.1.RELEASE</spring-security.version>
</properties>
…
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<version>${spring-security.version}</version>
</dependency>
Общий код для для тестирования контроллеров можно сделать абстрактным классом.
Подключение security к Spring MVC тестам стало немного проще:
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.apply(springSecurity()).build();
Для REST сервисов в проекте используется http-basic, поддержка которого в Spring Security Test есть среди многих других.
Для удобства создаем тестовые данные и делаем подключение httpBasic еще проще:
public static RequestPostProcessor userHttpBasic(User user) {
return SecurityMockMvcRequestPostProcessors
.httpBasic(user.getEmail(), user.getPassword());
}
mockMvc.perform(get(REST_URL).contentType(MediaType.APPLICATION_JSON)
.with(userHttpBasic(ADMIN)))
.andExpect(...
При этом в тестах не происходит подмена security context, как приходилось делать раньше, а честно выставляются httpBasic «Authorization» хедер.
Проверка JSON-содержимого ответа через собственный ResultMatcher
Обычная практика во всех руководствах по тестированию REST: использовние json-path для выборочной проверки полей содержимого ответа json (на полную проверку обычно терпения не хватает). Или использование более продвинутых библиотек, что сути подхода не меняет.
Однако гораздо лучше сравнить сразу весть объект со всеми вложенными уровнями иерархии! Т.к. строковое представление JSON объектов сравнивать нельзя (у него может быть разное форматирование и порядок полей), нужно сериализовать содержимое ответа JSON в объект и сравнивать объекты. Spring MVC интегрирован с JSON библиотекой Jackson, поэтому мы можем настроить ObjectMapper как нам удобно (например отключить сериализацию ленивой загрузки полей объектов Hibernate) и сделать удобный утильный класс для JSON сериализации-десериализации.
Для использования в тестах синтаксиса Spring Test:
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
делаем собственный ResultMatcher с методами contentMatcher и contentListMatcher сравнения объектов и списка объектов соответственно. Достаточно настроить этот Matcher для работы с нужными объектами (в простейшем случае, если у объекта ORM метод equals сделан по равенству PK и его нельзя использовать для сравнения, можно сравнивать объекты через toString):
public static final ModelMatcher<UserMeal, String> MATCHER =
new ToStringModelMatcher<>(UserMeal.class);
Для более сложных случаев для сравнения придется сделать обертку над ORM объектом.
Зато теперь тесты на проверку объекта JSON в ответе REST с любым уровнем вложений JSON объекта будут выглядеть очень просто:
mockMvc.perform(get(REST_URL + "by?email=" + USER.getEmail())
.with(userHttpBasic(ADMIN)))
.andExpect(MATCHER.contentMatcher(USER))
.andExpect(...
Тесты REST контроллеров проекта настроены на in-memory hsqldb, поэтому их можно запускать без подключения к DB.
Надеюсь что эти решения Вам пригодятся и спасибо за внимание.
vayho
Не по теме, но мы еще используем MapStruct для возврата плоских объектов JPA. А зависимости фетчим в запросе. Получается не очень гибко, но это одна большая проблема — возвращать(Rest) сложные JPA объекты на клиент не порождая кучу запросов к серверу.
А за библиотеку спасибо, раньше не видел, авторизация просто описана в базовом классе тестов.