1. Смысл REST Docs и базовая модель
Если вы когда‑нибудь видели README_API.md, который выглядит очень убедительно, но врёт в каждом втором примере — вы уже морально готовы к REST Docs. Ручная документация быстро расходится с реальным поведением сервиса: код меняют, тесты правят, а «описание API» остаётся лежать в репозитории как музейный экспонат эпохи “до рефакторинга”. Идея Spring REST Docs проста: источником документации становится проходящий тест, то есть сценарий, который действительно работает прямо сейчас.
Spring REST Docs не пытается угадывать ваш API по аннотациям и не делает «магическое сканирование контроллеров». Он честно говорит: “Покажи мне реальный HTTP‑запрос и реальный HTTP‑ответ (в тесте) — и я сохраню из этого кусочки документации”. Эти кусочки называются snippets (сниппеты). А дальше из сниппетов можно собрать документацию как конструктор: чем точнее тест, тем точнее docs.
Ментальная модель REST Docs
REST Docs удобно понимать не как «документацию», а как запись доказанного HTTP‑факта. В MVC‑тесте вы делаете запрос (через MockMvc/MockMvcTester), проверяете статус, заголовки и тело ответа, а затем добавляете маленькую команду: “сохрани сниппеты под таким именем”. Если тест зелёный — сниппеты появляются. Если тест красный — ничего не генерируется, и это хорошо: документация не должна рождаться из поломанного контракта.
Для новичка полезно представить это как конвейер:
flowchart LR A["MVC тест (MockMvcTester) делает HTTP вызов"] --> B["Assertions доказывают контракт"] B --> C["document(...) включает REST Docs"] C --> D["generated-snippets/ папки и файлы сниппетов"]
Обратите внимание на порядок: REST Docs стоит после assertions. Это важный этический принцип инженера: сначала мы убеждаемся, что контракт действительно такой, как мы ожидаем, и только потом начинаем “публиковать пресс‑релиз”.
Если говорить совсем просто, REST Docs — это не “генератор документации из воздуха”. Это механизм, который берёт реально выполненный запрос и реально полученный ответ, а потом превращает их в маленькие, удобные фрагменты документации.
2. REST Docs в тесте: @AutoConfigureRestDocs + MockMvcTester
Чтобы REST Docs вообще начал работать в Spring Boot тестах, нам нужно подключить его к MVC‑инфраструктуре. В Spring Boot это делается через аннотацию @AutoConfigureRestDocs. Она добавляет в тестовый контекст всё необходимое, чтобы document(...) мог перехватить выполненный запрос/ответ и записать сниппеты в output‑каталог.
Самый простой и «дешёвый» путь для документации публичного API ContentHub — это идти через @WebMvcTest, потому что мы документируем именно HTTP‑границу. То есть мы не обязаны поднимать базу данных, Security chain и весь контекст, если цель — зафиксировать внешний контракт public endpoint‑а.
Минимальный каркас теста выглядит так (обратите внимание: внутри уже есть знакомый MockMvcTester, а REST Docs добавляется буквально одной аннотацией):
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.assertj.MockMvcTester;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.test.web.servlet.assertj.MockMvcTester.assertThat;
// Поднимаем только MVC-слой (контроллер + MVC инфраструктура), без базы и прочего тяжёлого контекста
@WebMvcTest(PublicArticleController.class)
// Подключаем Spring REST Docs к MockMvc/MockMvcTester, чтобы можно было генерировать сниппеты
@AutoConfigureRestDocs
class PublicArticleRestDocsTest {
// MockMvcTester даёт удобный fluent API для запросов + assert'ов
@Autowired
MockMvcTester mvc;
@Test
void shouldDocumentPublicArticleList() {
// 1) Делаем реальный HTTP-вызов (в рамках MVC-теста)
// 2) Доказываем контракт через assertions
// 3) Только после этого сохраняем сниппеты REST Docs
assertThat(mvc.get().uri("/api/public/articles"))
.hasStatusOk() // важно: сначала проверка, потом генерация docs
.apply(document("public-article-list")); // имя папки со сниппетами
}
}
Здесь есть важная (и немного коварная) мысль. @WebMvcTest(PublicArticleController.class) поднимет только web‑слой, а зависимости контроллера (обычно сервисы) придётся подменить моками, иначе запрос просто некому будет обслужить. Для REST Docs это удобно: нам нужен проверенный HTTP‑контракт на границе, а не весь тяжёлый контекст приложения.
document(...) как обработчик результата
Когда вы видите document("public-article-list"), очень легко подумать, что это ещё одна assertion. На самом деле это другое: это обработчик результата, который говорит REST Docs: “Сохранить сниппеты для этого сценария”. В терминах Spring REST Docs это RestDocumentationResultHandler.
Если вы раньше писали raw MockMvc, то помните стиль:
// тот же принцип на raw MockMvc API
mockMvc.perform(get("/api/public/articles")) // выполняем HTTP-запрос
.andExpect(status().isOk()) // доказываем контракт: статус должен быть 200
.andDo(document("public-article-list")); // и только затем пишем сниппеты на диск
С MockMvcTester идея та же, просто API аккуратнее: мы строим assertion‑объект и применяем document(...) как дополнительное действие после успешной проверки.
Самое ценное здесь психологическое: документация не должна появляться «сама по себе» где‑то отдельно. Она появляется в точке, где вы уже доказали контракт. И если вы меняете контракт (например, поле в response DTO), тест падает, и «документация» автоматически перестаёт генерироваться, пока вы не обновите тест (а значит — не осознаете изменение контракта).
3. Централизация настроек REST Docs
Когда документации становится больше, появляется типичная проблема: разработчик устает придумывать имена сниппетов, забывает единый стиль, а потом в generated-snippets начинается лёгкий… творческий беспорядок. Чтобы этого избежать, REST Docs позволяет вынести общий обработчик и общую настройку в test‑конфигурацию.
RestDocsMockMvcConfigurationCustomizer и Markdown
REST Docs умеет генерировать сниппеты из шаблонов. Если вы хотите, чтобы сниппеты были в Markdown (а не в дефолтном формате), можно настроить template format централизованно через RestDocsMockMvcConfigurationCustomizer.
Вот компактный пример test‑конфигурации (она попадёт только в тестовый контекст):
import org.springframework.boot.test.autoconfigure.restdocs.RestDocsMockMvcConfigurationCustomizer;
import org.springframework.context.annotation.Configuration;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentationConfigurer;
import org.springframework.restdocs.templates.TemplateFormats;
// Тестовая конфигурация: влияет только на генерацию сниппетов, а не на поведение приложения
@Configuration(proxyBeanMethods = false)
class RestDocsConfig implements RestDocsMockMvcConfigurationCustomizer {
@Override
public void customize(MockMvcRestDocumentationConfigurer configurer) {
// Просим REST Docs генерировать сниппеты в Markdown-формате
// (чтобы потом их было удобно включать в .md документацию)
configurer.snippets().withTemplateFormat(TemplateFormats.markdown());
}
}
Здесь мы не «улучшаем тесты», мы наводим порядок в выходном формате артефактов. Важно, что сама идея REST Docs не зависит от формата: сниппеты — это сырьё. Как вы потом включите это сырьё в итоговый документ — отдельный инфраструктурный вопрос; здесь достаточно увидеть, что формат можно выровнять централизованно.
Чтобы конфигурация реально применялась в @WebMvcTest, её нужно импортировать:
import org.springframework.context.annotation.Import;
@WebMvcTest(PublicArticleController.class)
@AutoConfigureRestDocs
@Import(RestDocsConfig.class) // подключаем нашу тестовую конфигурацию REST Docs (Markdown-шаблоны и т.п.)
class PublicArticleRestDocsTest {
}
Общий RestDocumentationResultHandler: единое именование сниппетов
Вторая часто встречающаяся проблема — имена сниппетов. Если вы пишете document("public-article-list"), document("publicArticles"), document("list-articles") в разных местах, через неделю вы будете сами себя ненавидеть (и это ещё самый мягкий исход).
Хороший базовый трюк: использовать шаблон "{method-name}". Тогда имя сниппета будет совпадать с именем тестового метода, а вы уже умеете давать методам понятные имена (мы этим занимались ещё на JUnit‑днях).
Пример bean‑а, который создаёт общий handler:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;
@Configuration(proxyBeanMethods = false)
class RestDocsHandlers {
@Bean
RestDocumentationResultHandler restDocumentation() {
// Единый обработчик: имя сниппета будет совпадать с именем тестового метода.
// Это снижает шанс устроить зоопарк из "list", "list2", "final_list".
return MockMvcRestDocumentation.document("{method-name}");
}
}
В реальном тесте вы сможете применять этот handler как единую точку настройки вместо копипасты в каждом методе. Но для публичного API чаще удобнее явные path-like имена вроде public-articles-list и public-article-details: по ним дерево generated-snippets читается само. {method-name} остаётся нормальной альтернативой, если команда уже жёстко держит понятные имена тестовых методов и хочет убрать лишние строковые литералы.
4. Сниппеты на диске после зелёного теста
После успешного запуска теста REST Docs создаёт каталог со сниппетами. Чаще всего вы увидите путь вида build/generated-snippets (точное имя и расположение может быть настроено, но по умолчанию это выглядит именно так). Внутри появятся подпапки по имени сниппета, а в них — файлы с фрагментами документации.
Очень условно структура может выглядеть так:
build/generated-snippets └─ public-article-list ├─ http-request.md ├─ http-response.md └─ curl-request.md
То, что важно на этом этапе, — не заучить названия файлов, а понять принцип: один тестовый сценарий → один набор сниппетов. Сниппет — это не «документация всего API», а маленький модуль: запрос, ответ, параметры, поля, ошибки. Чем аккуратнее вы делите сценарии в тестах, тем аккуратнее получаются кирпичики документации.
И да, это тот редкий случай, когда фраза “побочный эффект тестов” звучит гордо. Обычно побочные эффекты — зло. Но здесь побочный эффект полезный: тесты производят артефакт, который нужен людям.
5. Типичные ошибки при первом использовании REST Docs
Первые попытки подключить REST Docs почти всегда идут по одному и тому же сюжету: “Я добавил @AutoConfigureRestDocs, но ничего не сгенерировалось”, “У меня куча сниппетов, но они странно названы”, “Документация получилась, но она описывает не то”. Это нормально: REST Docs — не сложный, но требовательный инструмент. Он любит дисциплину.
Ошибка №1: документирование до серьёзных assertions.
Иногда разработчик добавляет document(...) и забывает про смысл теста: проверка контракта должна быть основной, а документация — надстройкой. Если вы генерируете сниппеты, не проверив статус, content type и ключевые поля ответа, вы документируете “что-то”, а не контракт. В таком случае REST Docs превращается в принтер случайностей.
Ошибка №2: ожидание, что REST Docs «сам поймёт» ваш API.
REST Docs не читает ваши контроллеры и не анализирует DTO. Он работает по факту выполненного HTTP‑вызова. Если ваш тест не делает запрос (или делает его не тем способом), документации не будет. Если ваш тест делает запрос, но не контролирует ответ, документация будет такой же “управляемой”, как чат без модерации в пятницу вечером.
Ошибка №3: нестабильные или случайные имена сниппетов.
Сегодня вы назвали сниппет list, завтра list2, послезавтра final_list. В итоге у вас на диске хаос, а в документацию сложно понять, какой фрагмент откуда взялся. Лучше сразу выбрать модель именования: либо явные имена в одном стиле (public-article-list), либо {method-name} с хорошими именами тестовых методов.
Ошибка №4: попытка «документировать всё одной простынёй».
Хочется сделать один огромный тест “документирует весь public API”, потому что так меньше файлов. На практике это превращается в неперевариваемый сценарий, где смешаны разные ответы, разные коды ошибок и разные варианты поведения. REST Docs любит, когда сценарии маленькие и ясные: один endpoint, один контракт, один смысл.
Ошибка №5: включение REST Docs в самый дорогой тестовый уровень без причины.
Иногда REST Docs прикручивают сразу к @SpringBootTest со всем контекстом, базой, security и интеграциями. Работать будет, но это дорогой путь. Для публичных read‑endpoint‑ов обычно достаточно @WebMvcTest. Принцип «минимально достаточного теста» остаётся в силе даже для документации: документируем контракт на том уровне, где он проверяется дешевле всего.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ