# [Онлайн стажировка Spring 5/JPA Enterprise (TopJava)](http://javaops.ru/view/topjava)
## [Почему мы?](http://javaops.ru/#why)
## REST, REST-контроллеры, тестирование контроллеров Spring MVC
# Для просмотра открыты видео [4](#--4-миграция-на-junit-5), [5](#-5-принципы-rest-rest-контроллеры), [6](#-6-тестирование-rest-контроллеров-jackson), [7](#-7-кастомизация-jackson-object-mapper), [8](#user-content--8-тестирование-rest-контроллеров-через-jsonassert-и-матчеры)
- Не стоит стремиться прочитать все ссылки урока, их можно использовать как справочник. Гораздо важнее пройти основной материал урока и сделать домашнее задание
- Обязательно посмотри правила работы с патчами на проекте
- Делать Apply Patch лучше по одному непосредственно перед видео на эту тему, а при просмотре видео сразу отслеживать все изменения кода проекта по изменению в патче (`Version Control -> Local Changes -> Ctrl+D`)
- При первом Apply удобнее выбрать имя локального ченджлиста Name: Default. Далее все остальные патчи также будут в него попадать.
- Код проекта обновляется и не всегда совпадает с видео (можно увидеть, как развивался проект). Изменения в проекте указываю после соответствующего патча.
##  Разбор домашнего задания HW6
###  1. HW6
#### Apply 7_01_HW6_fix_tests.patch
#### Apply 7_02_HW6_meals.patch
> сделал фильтрацию еды через `get`: операция идемпотентная, можно делать в браузере обновление по F5
### Внимание: чиним пути в следующем патче
#### Apply 7_03_HW6_fix_relative_url_utf8.patch
-
Relative paths in JSP
-
Spring redirect: prefix
###  2. HW6 Optional
#### Apply 7_04_HW6_optional_add_role.patch
#### `JdbcUserServiceTest` отвалились. Будем чинить в `7_06_HW6_jdbc_transaction_roles.patch`
#### Apply 7_05_fix_hint_graph.patch
- В `JpaUserRepositoryImpl.getByEmail` DISTINCT попадает в запрос, хотя он там не нужен. Это просто указание Hibernate
не дублировать данные. Для оптимизации можно указать Hibernate делать запрос без
distinct: [15.16.2. Using DISTINCT with entity queries](https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#hql-distinct)
- Бага [HINT_PASS_DISTINCT_THROUGH does not work if 'hibernate.use_sql_comments=true'](https://hibernate.atlassian.net/browse/HHH-13280). При `hibernate.use_sql_comments=false` все работает - в SELECT нет DISTINCT.
- Тест `DataJpaUserServiceTest.getWithMeals()` не работает для admin (у админа 2 роли, и еда при JOIN дублируется). ...
#### Apply 7_06_HW6_jdbc_transaction_roles.patch
Еще интересные JDBC реализации: ...
### Валидация для `JdbcUserRepository` через Bean Validation API
#### Apply 7_07_HW6_optional_jdbc_validation.patch
- [Валидация данных при помощи Bean Validation API](https://alexkosarev.name/2018/07/30/bean-validation-api/).
На данный момент у нас реализована валидация сущностей только для jpa- и dataJpa-репозиториев. При работе
через JDBC-репозиторий может произойти попытка записи в БД некорректных данных, что приведет к `SQLException` из-за нарушения
ограничений, наложенных на столбцы базы данных. Для того чтобы перехватить невалидные данные еще до
обращения в базу, воспользуемся API *javax.validation* (ее реализация `hibernate-validator` используется для проверки данных в Hibernate и будет использоваться в Spring Validation, которую подключим позже).
В `ValidationUtil` создадим один потокобезопасный валидатор, который можно переиспользовать (см. *javadoc*).
С его помощью в методах сохранения и обновления сущности в jdbc-репозиториях мы можем производить валидацию этой сущности: `ValidationUtil.validate(object);`
Чтобы проверка не падала, `@NotNull Meal.user` пришлось пока закомментировать. Починим в 10-м занятии через `@JsonView`.
### Отключение кэша в тестах:
Вместо наших приседаний с `JpaUtil` и проверкой профилей мы можем ...
#### Apply 7_08_HW06_optional2_disable_tests_cache.patch
- [Example of PropertyOverrideConfigurer](https://www.concretepage.com/spring/example_propertyoverrideconfigurer_spring)
- [Spring util schema](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#xsd-schemas-util)
## Занятие 7:
###  3. Тестирование Spring MVC
Краткое содержание
#### Тестирование Spring MVC
Для более удобного сравнения объектов в тестах мы будем использовать библиотеку *Harmcrest* с Matcher'ами, которая
позволяет делать сложные проверки. С *JUnit* по умолчанию подтягивается *Harmcrest core*, но нам потребуется расширенная версия:
в `pom.xml` из зависимости JUnit исключим дочернюю `hamcrest-core` и добавим `hamcrest-all`.
Для тестирования web создадим вспомогательный класс `AbstractControllerTest`, от которого будут наследоваться все
тесты контроллеров. Его особенностью будет наличие `MockMvc` - эмуляции Spring MVC для тестирования web-компонентов.
Инициализируем ее в методе, отмеченном `@PostConstruct`:
```
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).addFilter(CHARACTER_ENCODING_FILTER).build();
```
Для того чтобы в тестах контроллеров не популировать базу перед каждым тестом, пометим этот базовый тестовый класс аннотацией `@Transactional`.
Теперь каждый тестовый метод будет выполняться в транзакции, которая будет откатываться после окончания метода и возвращать базу данных в исходное
состояние. Однако теперь в работе тестов могут возникнуть нюансы, связанные с пропагацией транзакций: все
транзакции репозиториев станут вложенными во внешнюю транзакцию теста. При этом, например, кэш первого уровня станет работать не
так, как ожидается. Т. е. при таком подходе нужно быть готовыми к ошибкам: мы их увидим и поборем в тестах на обработку ошибок на последних занятиях TopJava.
#### UserControllerTest
Создадим тестовый класс для контроллера юзеров, он должен наследоваться от `AbstractControllerTest`.
В `MockMvc` используется [паттерн проектирования Builder](https://refactoring.guru/ru/design-patterns/builder).
```
mockMvc.perform(get("/users")) // выполнить HTTP метод GET к "/users"
.andDo(print()) // распечатать содержимое ответа
.andExpect(status().isOk()) // от контроллера ожидается ответ со статусом HTTP 200(ok)
.andExpect(view().name("users")) // контроллер должен вернуть view с именем "users"
.andExpect(forwardedUrl("/WEB-INF/jsp/users.jsp")) // ожидается, что клиент должен быть перенаправлен на "/WEB-INF/jsp/users.jsp"
.andExpect(model().attribute("users", hasSize(2))) // в модели должен быть атрибут "users" размером = 2 ...
.andExpect(model().attribute("users", hasItem( // ... внутри которого есть элемент ...
allOf(
hasProperty("id", is(START_SEQ)), // ... с аттрибутом id = START_SEQ
hasProperty("name", is(USER.getName())) //... и name = user
)
)));
}
```
В параметры метода `andExpect()` передается реализация `ResultMatcher`, в которой мы определяем, как должен быть обработан ответ контроллера.
#### Apply 7_09_controller_test.patch
> - в `MockMvc` добавился `CharacterEncodingFilter`
> - добавил [`AllActiveProfileResolver`](//http://stackoverflow.com/questions/23871255/spring-profiles-simple-example-of-activeprofilesresolver) для возвращения массива профилей
> - сделал вспомогательный метод `AbstractControllerTest.perform()`
- Hamcrest
- Unit Testing of Spring MVC Controllers
###  4. [Миграция на JUnit 5](https://drive.google.com/open?id=16wi0AJLelso-dPuDj6xaGL7yJPmiO71e)
Краткое содержание
Для миграции на 5-ю версию JUnit в файле `pom.xml` поменяем зависимость `junit` на `junit-jupiter-engine` ([No need `junit-platform-surefire-provider` dependency in `maven-surefire-plugin`](https://junit.org/junit5/docs/current/user-guide/#running-tests-build-maven)).
Актуальную версию всегда можно посмотреть [в центральном maven-репозитории](https://search.maven.org/search?q=junit-jupiter-engine), берем только релизы (..-Mx означают предварительные milestone версии)
Изменять конфигурацию плагина `maven-sureface-plugin` в новых версиях JUnit уже не требуется.
JUnit5 не содержит в себе зависимости от *Harmcrest* (которую нам приходилось вручную
отключать для JUnit4 в предыдущих шагах), поэтому исключение `hamcrest-core` просто удаляем.
В итоге у нас останутся зависимости JUnit5 и расширенный Harmcrest.
Теперь мы можем применить все нововведения пятой версии в наших тестах:
1. Для всех тестов теперь мы можем удалить `public`.
2. Аннотацию `@Before` исправим на `@BeforeEach` - теперь метод, который будет выполняться перед
каждым тестом, помечается именно так.
3. В JUnit5 работа с исключениями похожа на JUnit4 версии 4.13: вместо ожидаемых исключений в параметрах аннотации `@Test(expected = Exception.class)` используется метод `assertThrows()`,
в который первым аргументом мы передаем ожидаемое исключение, а вторым аргументом — реализацию функционального интерфейса `Executable` (код,
в котором ожидается возникновение исключения).
4. Метод `assertThrows()` возвращает исключение, которое было выброшено в переданном ему коде. Теперь мы можем получить это исключение, извлечь из него сообщение с помощью
`e.getMessage()` и сравнить с ожидаемым.
5. Для теста на валидацию при проверке предусловия, только при выполнении которого
будет выполняться следующий участок кода (например, в нашем случае тесты на валидацию выполнялись
только в jpa профиле), теперь нужно пользоваться утильным методом `Assumptions` (нам уже не требуется).
6. Проверку Root Cause - причины, из-за которой было выброшено пойманное исключение, мы будем делать позднее, при тестах на ошибки.
7. Из JUnit5 исключена функциональность `@Rule`, вместо них теперь нужно использовать `Extensions`, которые
могут встраиваться в любую фазу тестов. Чтобы добавить их в тесты, пометим базовый тестовый класс аннотацией `@ExtendWith`.
JUnit предоставляет нам набор коллбэков — интерфейсов, которые будут исполняться в определенный момент тестирования.
Создадим класс `TimingExtension`, который будет засекать время выполнения тестовых методов.
Этот класс будет имплементировать маркерные интерфейсы — коллбэки JUnit:
- `BeforeTestExecutionCallback` - коллбэк, который будет вызывать методы этого интерфейса перед каждым тестовым методом.
- `AfterTestExecutionCallback` - методы этого интерфейса будут вызываться после каждого тестового метода;
- `BeforeAllCallback` - методы перед выполнением тестового класса;
- `AfterAllCallback` - методы после выполнения тестового класса;
Осталось реализовать соответствующие методы, которые описываются в каждом из этих интерфейсов, они и будут вызываться JUnit в нужный момент:
- в методе `beforeAll` (который будет вызван перед запуском тестового класса) создадим спринговый утильный секундомер `StopWatch` для текущего тестового класса;
- в методе `beforeTestExecution` (будет вызван перед тестовым методом) - запустим секундомер;
- в методе `afterTestExecution` (будет вызван после тестового метода) - остановим секундомер.
- в методе `afterAll` (который будет вызван по окончанию работы тестового класса) - выведем результат работы этого секундомера в лог;
8. Аннотации `@ContextConfiguration` и `@ExtendWith(SpringExtension.class)` (замена `@RunWith`) мы можем заменить одной `@SpringJUnitConfiguration` (старые версии IDEA ее не понимают)
#### Apply 7_10_JUnit5.patch
> - [No need `junit-platform-surefire-provider` dependency in `maven-surefire-plugin`](https://junit.org/junit5/docs/current/user-guide/#running-tests-build-maven)
> - [Наконец пофиксили баг с `@SpringJUnitConfig`](https://youtrack.jetbrains.com/issue/IDEA-166549)
- [JUnit 5 homepage](https://junit.org/junit5)
- [Overview](https://junit.org/junit5/docs/snapshot/user-guide/#overview)
- [10 интересных нововведений](https://habr.com/post/337700)
- Дополнительно:
- [Extension Model](https://junit.org/junit5/docs/current/user-guide/#extensions)
- [A Guide to JUnit 5](http://www.baeldung.com/junit-5)
- [Migrating from JUnit 4](http://www.baeldung.com/junit-5-migration)
- [Before and After Test Execution Callbacks](https://junit.org/junit5/docs/snapshot/user-guide/#extensions-lifecycle-callbacks-before-after-execution)
- [Conditional Test Execution](https://junit.org/junit5/docs/snapshot/user-guide/#writing-tests-conditional-execution)
- [Third party Extensions](https://github.com/junit-team/junit5/wiki/Third-party-Extensions)
- [Реализация assertThat](https://stackoverflow.com/questions/43280250)
###  5. [Принципы REST. REST контроллеры](https://drive.google.com/open?id=1e4ySjV15ZbswqzL29UkRSdGb4lcxXFm1)
Краткое содержание
#### Принципы REST, REST-контроллеры
> [REST](http://spring-projects.ru/understanding/rest/) - архитектурный стиль проектирования распределенных систем (типа клиент-сервер).
Чаще всего в REST-сервер и клиент общаются посредством обмена JSON-объектами через HTTP-методы GET/POST/PUT/DELETE/PATCH.
Особенностью REST является отсутствие состояния (контекста) взаимодействий клиента и сервера.
В нашем приложении есть контроллеры для Admin и для User. Чтобы сделать их REST-контроллерами,
заменим аннотацию `@Controller` на `@RestController`
> Не поленитесь зайти через Ctrl+click в `@RestController`: к аннотации `@Controller` добавлена `@ResponseBody`. Т. е. ответ от нашего приложения будет не View, а данные в теле ответа.
В `@RequestMapping`, кроме пути для методов контроллера (`value`), добавляем параметр `produces = MediaType.APPLICATION_JSON_VALUE`.
Это означает, что в заголовки ответа будет добавлен тип `ContentType="application/json"` - в ответе от контроллера будет приходить JSON-объект.
> Чтобы было удобно использовать путь к этому контроллеру в приложении и в тестах,
> выделим путь к нему в константу REST_URL, к которой можно будет обращаться из других классов
1. Метод `AdminRestController.getAll` пометим аннотацией `@GetMapping` - маршрутизация к методу по HTTP GET.
2. Метод `AdminRestController.get` пометим аннотацией `@GetMapping("/{id}")`.
В скобках аннотации указано, что к основному URL контроллера будет добавляться `id` пользователя - переменная, которая передается в запросе непосредственно в URL.
Соответствующий параметр метода нужно пометить аннотацией `@PathVariable` (если имя в URL и имя аргумента метода не совпадают, в параметрах аннотации дополнительно нужно будет уточнить
имя в URL. Если они совпадают, [этого не требуется](https://habr.com/ru/post/440214/).
3. Метод создания пользователя `create` отметим аннотацией `@PostMapping` - маршрутизация к методу по HTTP POST.
В метод мы передаем объект `User` в теле запроса (аннотация `@RequestBody`) в формате JSON (`consumes = MediaType.APPLICATION_JSON_VALUE`).
При создании нового ресурса правило хорошего тона - вернуть в заголовке ответа URL созданного ресурса.
Для этого возвращаем не `User`, а `ResponseEntity`, который мы можем с помощью билдера `ServletUriComponentsBuilder` дополнить заголовком ответа `Location` и вернуть статус `CREATED(201)`
(если пойти в код `ResponseEntity.created` можно докопаться до сути, очень рекомендую смотреть в исходники кода).
4. Метод `delete` помечаем `@DeleteMapping("/{id}")` - HTTP DELETE.
Он ничего не возвращает, поэтому помечаем его аннотацией `@ResponseStatus(HttpStatus.NO_CONTENT)`. Статус ответа будет HTTP.204;
5. Над методом обновления ставим `@PutMapping` (HTTP PUT). В аргументах метод принимает `@RequestBody User user` и `@PathVariable int id`.
6. Метод поиска по `email` также помечаем `@GetMapping` и, чтобы не было конфликта маршрутизации с методом `get()`,
указываем в URL добавку `/by`. В этот метод `email` передается как параметр запроса (аннотация `@RequestParam`).
> **Все это СТАНДАРТ архитектурного стиля REST. НЕ придумывайте ничего своего в своих выпускных проектах! Это очень большая ошибка - не придерживаться стандартов API.**
7. `ProfileRestController` выполняем аналогичным способом с учетом того, что пользователь имеет доступ только к своим данным.
Если на данном этапе попытаться запустить приложение и обратиться к какому-либо методу контроллера, сервер ответит нам ошибкой со статусом 406,
так как Spring не знает, как преобразовать объект User в JSON...
#### Apply 7_11_rest_controller.patch
- Понимание REST
- JSON (JavaScript Object Notation)
- [15 тривиальных фактов о правильной работе с протоколом HTTP](https://habrahabr.ru/company/yandex/blog/265569/)
- [10 Best Practices for Better RESTful](https://medium.com/@mwaysolutions/10-best-practices-for-better-restful-api-cbe81b06f291)
- [Best practices for rest nested resources](https://stackoverflow.com/questions/20951419/what-are-best-practices-for-rest-nested-resources)
-
Request mapping
- [Лучшие практики разработки REST API: правила 1-7,15-17](https://tproger.ru/translations/luchshie-praktiki-razrabotki-rest-api-20-sovetov/)
- Дополнительно:
- [Подборка практик REST](https://gist.github.com/Londeren/838c8a223b92aa4017d3734d663a0ba3)
- JAX-RS vs Spring MVC
- RESTful API для сервера – делаем правильно (Часть 1)
- RESTful API для сервера – делаем правильно (Часть 2)
- И. Головач.
RestAPI
- [value/name в аннотациях @PathVariable и @RequestParam](https://habr.com/ru/post/440214/)
###  6. [Тестирование REST контроллеров. Jackson.](https://drive.google.com/open?id=1aZm2qoMh4yL_-i3HhRoyZFjRAQx-15lO)
Краткое содержание
Для работы с JSON добавляем в `pom.xml` зависимость `jackson-databind`.
Актуальную версию библиотеки можно посмотреть в [центральном maven-репозитории](https://search.maven.org/artifact/com.fasterxml.jackson.core/jackson-databind).
Теперь Spring будет автоматически использовать эту библиотеку для сериализации/десериализации объектов в JSON (найдя ее в *classpath*).
Если сейчас запустить приложение и обратиться к методам REST-контроллера, то оно выбросит `LazyInitializationException`.
Оно возникает из-за того, что у наших сущностей есть лениво загружаемые поля, отмеченные `FetchType.LAZY` - при загрузке сущности из базы вместо такого поля подставится Proxy, который и должен вернуть
реальный экземпляр этого поля при первом же обращении. Jackson при сериализации в JSON использует все поля сущности,
и при обращении к *Lazy*-полям возникает исключение, так как сессия работы с БД в этот момент уже закрыта, и нужный объект
не может быть инициализирован. Чтобы Jackson игнорировал эти поля, пометим их аннотацией `@JsonIgnore`.
Теперь при запуске приложения REST-контроллер будет работать. Но при получении JSON-объектов мы можем увидеть, что Jackson сериализовал объект
через геттеры (например, в ответе есть поле `new` от метода `Persistable.isNew()`).
Чтобы учитывались только поля объектов, добавим над `AbstractBaseEntity`:
````java
@JsonAutoDetect(fieldVisibility = ANY, // jackson видит все поля
getterVisibility = NONE, // ... но не видит геттеров
isGetterVisibility = NONE, //... не видит геттеров boolean-полей
setterVisibility = NONE) // ... не видит сеттеров
````
Теперь все сущности, унаследованные от базового класса, будут сериализоваться/десериализоваться через поля.
#### Apply 7_12_rest_test_jackson.patch
- [Jackson databind github](https://github.com/FasterXML/jackson-databind)
- [Jackson Annotation Examples](https://www.baeldung.com/jackson-annotations)
###  7. [Кастомизация Jackson Object Mapper](https://drive.google.com/open?id=1CM6y1JhKG_yeLQE_iCDONnI7Agi4pBks)
Краткое содержание
Сейчас, чтобы не сериализовать *Lazy*-поля, мы должны пройтись по каждой сущности и
вручную пометить их аннотацией `@JsonIgnore`. Это неудобно, засоряет код и допускает возможные ошибки. К тому же,
при некоторых условиях, нам иногда нужно загрузить и в ответе передать эти *Lazy*-поля.
Чтобы запретить сериализацию Lazy-полей для всего проекта, подключим в `pom.xml` библиотеку `jackson-datatype-hibernate`.
Также изменим сериализацию/десериализацию полей объектов в JSON: не через аннотацию `@JsonAutoDetect`, а в классе `JacksonObjectMapper`, который
унаследуем от `ObjectMapper` (стандартный Mapper, который использует Jackson) и сделаем в нем другие настройки.
В конструкторе:
- регистрируем `Hibernate5Module` - модуль `jackson-datatype-hibernate`, который не делает сериализацию ленивых полей.
- модуль для корректной сериализации `LocalDateTime` в поля JSON - `JavaTimeModule` модуль библиотеки `jackson-datatype-jsr310`
- запрещаем доступ ко всем полям и методам класса и потом разрешаем доступ только к полям
- не сериализуем null-поля (`setSerializationInclusion(JsonInclude.Include.NON_NULL)`)
Чтобы подключить наш кастомный `JacksonObjectMapper` в проект, в конфигурации `spring-mvc.xml` к
настройке `` добавим `MappingJackson2HttpMessageConverter`, который будет использовать наш маппер.
#### Apply 7_13_jackson_object_mapper.patch
- Сериализация hibernate lazy-loading с помощью
jackson-datatype-hibernate
- Handle Java 8 dates with Jackson
- Дополнительно:
- Jackson JSON
Serializer & Deserializer
###  8. [Тестирование REST-контроллеров через JSONassert и Матчеры](https://drive.google.com/open?id=1oa3e0_tG57E71g6PW7_tfb3B61Qldctl)
Краткое содержание
Сейчас в тестах REST-контроллера мы проводим проверку только на статус ответа и тип возвращаемого контента. Добавим проверку содержимого ответа.
#### 7_14_json_assert_tests
Чтобы сравнивать содержимое ответа контроллера в виде JSON и сущность, воспользуемся библиотекой
`jsonassert`, которую подключим в `pom.xml` со scope *test*.
Эта библиотека при сравнении в тестах в качестве ожидаемого значения ожидает от
нас объект в виде JSON-строки. Чтобы вручную не преобразовывать объекты в JSON и не
хардкодить их в виде строк в наши тесты, воспользуемся Jackson.
Для преобразования объектов в JSON и обратно создадим утильный класс `JsonUtil`, в котором
с помощью нашего `JacksonObjectMapper` и будет конвертировать объекты.
И мы сталкиваемся с проблемой: `JsonUtil` - утильный класс и не является
бином Spring, а для его работы требуется наш кастомный маппер, который находится под управлением
Spring и расположен в контейнере зависимостей. Поэтому, чтобы была возможность получить
наш маппер из других классов, сделаем его синглтоном и сделаем в нем статический
метод, который будет возвращать его экземпляр. Теперь `JsonUtil` сможет его получить.
И нам нужно указать Spring, чтобы он не создавал второй экземпляр этого объекта, а клал в свой контекст существующий.
Для этого в конфигурации `spring-mvc.xml` определим factory-метод, с помощью которого Spring должен
получить экземпляр (instance) этого класса:
```xml
```
а в конфигурации `message-converter` вместо создания бина просто сошлемся на сконфигурированный `objectMapper`.
Метод `ContentResultMatchers.json()` из `spring-test` использует библиотеку `jsonassert` для сравнения 2-х JSON строк: одну из ответа контроллера и вторую -
JSON-сериализация `admin` без поля `registered` (это поле инициализируется в момент создания и отличается).
В методе `JsonUtil.writeIgnoreProps` мы преобразуем объект `admin` в мапу, удаляем из нее игнорируемые поля и снова сериализуем в JSON.
Также сделаем тесты для утильного класса `JsonUtil`. В тестах мы записываем
объект в JSON-строку, затем конвертируем эту строку обратно в объект и сравниваем с исходным. И то же самое делаем со списком объектов.
#### 7_15_tests_refactoring
**`RootControllerTest`**
Сделаем рефакторинг `RootControllerTest`. Ранее мы в тесте получали модель, доставали из нее сущности и с помощью `hamcrest-all`
производили по одному параметру их сравнение с ожидаемыми значениями.
Метод `ResultActions.andExpect()` позволяет передавать реализацию интерфейса `Matcher`, в котором можно делать любые сравнения.
Функциональность сравнения списка юзеров по ВСЕМ полям у нас уже есть - мы просто делегируем сравнение объектов в `UserTestData.MATCHER`.
При этом нам больше не нужен `harmcrest-all`, нам достаточно только `harmcrest-core`.
**`MatcherFactory`**
Теперь вместо `jsonassert` и сравнения JSON-строк в тестах контроллеров сделаем сравнения JSON-объектов через `MatcherFactory`.
Преобразуем ответ контроллера из JSON в объект и сравним с эталоном через уже имеющийся у нас матчер.
Вместо сравнения JSON-строк в метод `andExpect()` мы будем передавать реализации интерфейса `ResultMatcher` из `MATCHER.contentJson(..)`.
`MATCHER.contentJson(..)` принимают ожидаемый объект и возвращают для него `ResultMatcher` с реализацией единственного метода `match(MvcResult result)`,
в котором делегируем сравнение уже существующим у нас матчерам.
Мы берем JSON-тело ответа (`MatcherFactory.getContent`), десериализуем его в объект (`JsonUtil.readValue/readValues`) и сравниваем через имеющийся `MATCHER.assertMatch`
десериализованный из тела контроллера объект и ожидаемое значение.
> Методы из класса `TestUtil` перенес в `MatcherFactory`, лишние удалил.
**`AdminRestControllerTest`**
- `getByEmail()` - сделан по аналогии с тестом `get()`. Дополнительно нужно добавить в строку URL параметры запроса.
- `delete()` - выполняем HTTP.DELETE. Проверяем статус ответа 204. Проверяем, что пользователь удален.
> Раньше я получал всех users из базы и проверял, что среди них нет удаленного. При этом тесты становятся чувствительными ко всем users в базе и ломаются при добавлении/удалении новых тестовых данных.
- `update()` - выполняем HTTP.PUT. В тело запроса подаем сериализованный `JsonUtil.writeValue(updated)`. После выполнения проверяем, что объект в базе обновился.
- `create()` - выполняем HTTP.POST аналогично `update()`. Но сравнить результат мы сразу не можем, т. к. при создании объекта ему присваивается `id`.
Поэтому мы извлекаем созданного пользователя из ответа (`MATCHER.readFromJson(action)`), получаем его `id`, и уже с этим `id` эталонный объект мы можем сравнить с объектом в ответе контроллера и со
значением в базе.
- `getAll()` - аналогично `get()`. Список пользователей из ответа в формате JSON сравниваем с эталонным списком (`MATCHER.contentJson(admin, user)`).
Тесты для `ProfileRestController` выполнены аналогично.
#### Apply 7_14_json_assert_tests.patch
> - В `JsonUtil.writeIgnoreProps` вместо цикла по мапе сделал `map.keySet().removeAll`
- [JSONassert](https://github.com/skyscreamer/JSONassert)
- [Java Code Examples for ObjectMapper](https://www.programcreek.com/java-api-examples/index.php?api=com.fasterxml.jackson.databind.ObjectMapper)
#### Apply 7_15_tests_refactoring.patch
> - Методы из класса `TestUtil` перенес в `MatcherFactory`, лишние удалил.
> - Раньше в тестах я для проверок получал всех users из базы и сравнивал с эталонным списком. При этом тесты становятся чувствительными ко всем users в базе и ломаются при добавлении/удалении новых тестовых данных.
- [Java @SafeVarargs Annotation](https://www.baeldung.com/java-safevarargs)
###  9. Тестирование через SoapUI. UTF-8
Краткое содержание
SoapUI - это один из инструментов для тестирования API приложений, которые работают по REST и по SOAP.
Он позволяет нам по протоколу HTTP дернуть методы нашего API и увидеть ответ контроллеров.
Если в контроллер мы добавим метод, который в теле ответа будет возвращать текст на кириллице, то увидим, что кодировка теряется.
Для сохранения кодировки используем `StringHttpMessageConverter`, который конфигурируем в `spring-mvc.xml`.
При этом мы должны явно указать, что конвертор будет работать только с текстом в кодировке *UTF-8*.
#### Apply 7_16_soapui_utf8_converter.patch
- Инструменты тестирования REST:
- SoapUI
- Написание HTTP-запросов с помощью
Curl.
Для Windows 7 можно использовать Git Bash, с Windows 10 v1803 можно прямо из консоли. Возможны проблемы с UTF-8:
- [CURL doesn't encode UTF-8](https://stackoverflow.com/a/41384903/548473)
- [Нстройка кодировки в Windows](https://support.socialkit.ru/ru/knowledge-bases/4/articles/11110-preduprezhdenie-obnaruzhenyi-problemyi-svyazannyie-s-raspoznavaniem-russkih-simvolov)
- **[IDEA: Tools->HTTP Client->...](https://www.jetbrains.com/help/idea/rest-client-tool-window.html)**
- Postman
- [Insomnia REST client](https://insomnia.rest/)
**Импортировать проект в SoapUI из `config\Topjava-soapui-project.xml`. Response смотреть в формате JSON.**
> Проверка UTF-8: http://localhost:8080/topjava/rest/profile/text
[ResponseBody and UTF-8](http://web.archive.org/web/20190102203042/http://forum.spring.io/forum/spring-projects/web/74209-responsebody-and-utf-8)
##  Ваши вопросы
> Зачем у нас и UI-контроллеры, и REST-контроллеры? То есть в общем случае backend-разработчику недостаточно предоставить REST API и RestController?
В общем случае нужны и те, и другие. REST обычно используют для отдельного UI (например, на React или Angular) или для
интеграции / мобильного приложения. У нас REST-контроллеры используются только для тестирования. UI-контроллеры используем для
нашего приложения на JSP шаблонах. Таких сайтов без богатой UI логики тоже немало. Например https://javaops.ru/ :)
Разница в запросах:
- для UI используются только GET и POST
- при создании/обновлении в UI мы принимаем данные из формы `application/x-www-form-urlencoded` (посмотрите
вкладку `Network`, не в формате JSON)
- для REST API запросы GET, POST, PUT, DELETE, PATCH и возвращают только данные (обычно JSON)
...и в способе авторизации:
- для REST API у нас будет базовая авторизация
- для UI - через cookies
Также часто бывают смешанные сайты, где есть и отдельное JS приложение, и шаблоны.
> При выполнении тестов через MockMvc никаких изменений на базе не видно, почему оно не сохраняет?
`AbstractControllerTest` аннотируется `@Transactional` - это означает, что тесты идут в транзакции, и после каждого
теста JUnit делает rollback базы.
> Что получается в результате выполнения запроса `SELECT DISTINCT(u) FROM User u LEFT JOIN FETCH u.roles ORDER BY u.name, u.email`? В чем разница в SQL без `DISTINCT`.
Запросы SQL можно посмотреть в логах. Т. е. `DISTINCT` в `JPQL` влияет на то, как Hibernate обрабатывает дублирующиеся
записи (с `DISTINCT` их исключает). Результат можно посмотреть в тестах или приложении, поставив брекпойнт. По
поводу `SQL DISTINCT` не стесняйтесь пользоваться google,
например, [оператор SQL DISTINCT](http://2sql.ru/novosti/sql-distinct/)
> В чем заключается расширение функциональности hamcrest в нашем тесте, что нам пришлось его отдельно от JUnit прописывать?
hamcrest-all используется в проверках `RootControllerTest`: `org.hamcrest.Matchers.*`
> Jackson мы просто подключаем в помнике, и Spring будет с ним работать без любых других настроек?
Да, Spring смотрит в classpath и если видит там Jackson, то подключает интеграцию с ним.
> Где-то слышал, что любой ресурс по REST должен однозначно идентифицироваться через url без параметров. Правильно ли задавать URL для фильтрации в виде `http://localhost/topjava/rest/meals/filter/{startDate}/{startTime}/{endDate}/{endTime}` ?
Так делают только при
отношении
агрегация, например, если давать админу право смотреть еду любого юзера, URL мог бы быть похож
на `http://localhost/topjava/rest/users/{userId}/meals/{mealId}` (не рекомендуется, см. ссылку ниже). В случае критериев
поиска или страничных данных они передаются как параметр. Смотри также:
- [15 тривиальных фактов о правильной работе с протоколом HTTP](https://habrahabr.ru/company/yandex/blog/265569/)
- 10 Best Practices
for Better RESTful
- [REST resource hierarchy (если кратко: не рекомендуется)](https://stackoverflow.com/questions/15259843/how-to-structure-rest-resource-hierarchy)
> Что означает конструкция в `JsonUtil`: `reader.readValues(json)`;
См. Generic Methods. Когда компилятор
не может вывести тип, можно его уточнить при вызове generic метода. Неважно, static или нет.
##  Домашнее задание HW07
- 1: Добавить тесты контроллеров:
- 1.1 `RootControllerTest.getMeals` для `meals.jsp`
- 1.2 Сделать `ResourceControllerTest` для `style.css` (проверить `status` и `ContentType`)
- 2: Реализовать `MealRestController` и протестировать его через `MealRestControllerTest`
- 2.1 следите, чтобы url в тестах совпадал с параметрами в методе контроллера. Можно добавить
логирование `` для проверки маршрутизации.
- 2.2 в параметрах `getBetween` принимать `LocalDateTime` (конвертировать
через @DateTimeFormat with Java
8 Date-Time API), пока без проверки на `null` (используя `toLocalDate()/toLocalTime()`, см. Optional п. 3). В
тестах передавать в формате `ISO_LOCAL_DATE_TIME` (
например `'2011-12-03T10:15:30'`).
### Optional
- 3: Переделать `MealRestController.getBetween` на параметры `LocalDate/LocalTime` c раздельной фильтрацией по
времени/дате, работающий при `null` значениях (см. демо и `JspMealController.getBetween`)
. Заменить `@DateTimeFormat` на свои LocalDate/LocalTime конверторы или форматтеры.
- Spring Type
Conversion
- Spring Field
Formatting
-
Difference between Spring MVC formatters and converters
- 4: Протестировать `MealRestController` (SoapUI, Curl, IDEA Test RESTful Web Service, Postman). Запросы `curl` занести
в отдельный `md` файл (или `README.md`)
- 5: Добавить в `AdminRestController` и `ProfileRestController` методы получения пользователя вместе с
едой (`getWithMeals`, `/with-meals`).
- [Jackson – Bidirectional Relationships](https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion)
### Optional 2
- 6: Сделать тесты на методы контроллеров `getWithMeals()` (п. 5)
**На следующем занятии используется JavaScript/jQuery. Если у вас там
пробелы, пройдите его основы**
---------------------
##  Типичные ошибки и подсказки по реализации
- 1: Ошибка в тесте _Invalid read array from JSON_ обычно расшифровывается немного ниже: читайте внимательно.
- 2: Jackson и неизменяемые объекты (для
сериализации `MealTo`)
- 3: Если у meal, приходящий в контроллер, поля `null`, проверьте `@RequestBody` перед параметром (данные приходят в
формате JSON)
- 4: При проблемах с собственным форматтером убедитесь, что в конфигурации `