Опирается на правила:
TS-15…TS-18из Test Strategy Style Guide → раздел 5. Один тест.
Важно знать
- Тест extends
<Domain>BaseIntegrationTest, инжектитTestRestTemplate+DatabasePreparer.- Имена методов:
<action>_when<Condition>_<expectedResult>.@DisplayNameс BR-кодом обязательно:@DisplayName("BR-002: ...").- HTTP через
TestRestTemplate.exchange— точный контроль над methods/headers/body.MockMvcоставляем для unit-тестов контроллера без БД.- JWT через
TestHttpHeaders.*хелперы, не руками в каждом тесте.- AAA-структура (Arrange / Act / Assert) с пустыми строками между блоками.
Полный пример одного теста. Это шаблон, на который ориентируются все integration-тесты в сервисе.
Полный пример
public class ConfirmOrderEndpointIntegrationTest extends OrderBaseIntegrationTest {
private static final String BASE_URL = "/v1/orders";
@Autowired private TestRestTemplate restTemplate;
@Autowired private OrderDatabasePreparer databasePreparer;
@Autowired private DSLContext dsl;
@Test
@DisplayName("BR-002: confirm fails when reservation failed")
void confirmOrder_whenReservationFailed_returns409() {
// Arrange
var orderId = UUID.fromString("11111111-1111-1111-1111-111111111111");
var now = OffsetDateTime.parse("2026-05-26T10:00:00Z");
given(uuidGenerator.generate()).willReturn(orderId);
given(dateTimeService.getCurrentDateTimeInUTC()).willReturn(now);
var failedOrder = new OrderTestObjectGenerator()
.withId(orderId)
.withStatus(OrderStatus.RESERVATION_FAILED)
.generate();
databasePreparer.createOrder(failedOrder).prepare();
// Act
var response = restTemplate.exchange(
BASE_URL + "/" + orderId + "/confirm",
HttpMethod.POST,
new HttpEntity<>(TestHttpHeaders.withSuccessToken()),
ProblemDetailJsonBean.class
);
// Assert
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CONFLICT);
assertThat(response.getBody().getCode()).isEqualTo("OUT_OF_STOCK");
var orders = dsl.selectFrom(ORDERS)
.where(ORDERS.ID.eq(orderId))
.fetch();
assertThat(orders).hasSize(1);
assertThat(orders.get(0).getStatus()).isEqualTo("RESERVATION_FAILED");
}
}
Что есть:
- Наследуется от
OrderBaseIntegrationTest(получает@MockitoBean-ы,@ServiceConnection PostgreSQL). @AutowiredHTTP-клиент и preparer.@DisplayNameс BR-кодом — связывает тест со спецификацией.- AAA-структура с пустыми строками.
- Asserts на response + state в БД.
Наследование от доменного base
TS-15: extends <Domain>BaseIntegrationTest.
public class CreateOrderEndpointIntegrationTest extends OrderBaseIntegrationTest {
// получает:
// - @ServiceConnection PostgreSQL
// - @MockitoBean DateTimeService, UuidGenerator
// - @Import(TestJwtConfiguration.class)
// - @ActiveProfiles("integration-test")
// - @BeforeEach clearDatabase()
}
Не дублируем эти аннотации в каждом тесте — они в BaseIntegrationTest.
Имена методов
TS-16: формат <action>_when<Condition>_<expectedResult>.
// Хорошо
void confirmOrder_whenDraft_returns200()
void confirmOrder_whenReservationFailed_returns409()
void createOrder_whenInvalidEmail_returns400()
void getOrder_whenNotFound_returns404()
void cancelOrder_whenAlreadyCancelled_returns409()
// Тоже хорошо — длинное говорящее имя
void shouldFailWith409WhenConfirmingOrderThatHasFailedReservation()
@DisplayName с BR-кодом:
@Test
@DisplayName("BR-002: confirm fails when reservation failed")
void confirmOrder_whenReservationFailed_returns409() { ... }
BR-002 — Business Rule из спецификации (см. Спецификация). Это создаёт traceability: spec → test → код. Изменили BR-002 → видно, какие тесты нужно обновить.
TestRestTemplate.exchange
TS-17: HTTP-вызов с полным контролем.
var response = restTemplate.exchange(
BASE_URL + "/" + orderId + "/confirm",
HttpMethod.POST,
new HttpEntity<>(requestBody, TestHttpHeaders.withSuccessToken()),
OrderResponse.class
);
Что есть:
- URL — полный путь, без mock router.
- HttpMethod — явный method.
- HttpEntity — body + headers.
- Response type — automatic JSON deserialization.
ResponseEntity<OrderResponse> response = restTemplate.exchange(...);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody().orderId()).isEqualTo(orderId);
assertThat(response.getHeaders().getFirst("X-Request-Id")).isNotNull();
MockMvc vs TestRestTemplate
| Tool | Когда |
|---|---|
TestRestTemplate | Integration-тест с полным Spring context + PostgreSQL |
MockMvc | Unit-тест контроллера без БД (только тестирование сериализации + RBAC) |
MockMvc короче (.perform(post(...).header(...))), но не запускает Tomcat / реальный HTTP stack. Sphinx serialization, headers, CORS — может работать иначе в проде. TestRestTemplate пробрасывает запрос через реальный Tomcat — выше точность.
Для integration-тестов в UCP — TestRestTemplate.
TestHttpHeaders.*
TS-18: JWT через хелперы.
// Хорошо
new HttpEntity<>(TestHttpHeaders.withSuccessToken());
new HttpEntity<>(TestHttpHeaders.withAdminToken());
new HttpEntity<>(TestHttpHeaders.withCustomerToken(customerId));
new HttpEntity<>(TestHttpHeaders.withSellerToken(sellerId));
// Плохо — токены руками
var headers = new HttpHeaders();
headers.setBearerAuth("eyJhbGciOiJSUzI1NiJ9..."); // hardcoded JWT
new HttpEntity<>(headers);
Helper-методы:
withSuccessToken()— generic customer-роль.withAdminToken()— admin.withCustomerToken(customerId)— токен сsub=customerId.withSellerToken(sellerId)— seller-роль сsub=sellerId.
Это даёт:
- Единый source-of-truth: меняем JWT-структуру в одном месте.
- Тесты на ABAC: можно явно положить разный
subв токен.
См. BaseIntegrationTest → TestJwtConfiguration.
AAA с пустыми строками
TS-3: визуальное разделение.
@Test
void test() {
// Arrange
var x = ...;
var y = ...;
setupDb();
// Act
var result = sut.do(...);
// Assert
assertThat(result).isEqualTo(...);
}
Комментарии // Arrange, // Act, // Assert — не обязательны, но пустая строка между блоками — да. Это сигнал «новая фаза теста».
Что запрещено
| Антипаттерн | Правило | Что взамен |
|---|---|---|
Тест не extends <Domain>BaseIntegrationTest | TS-15 | наследование обязательно |
@DisplayName без BR-кода | TS-16 | BR-NNN: ... |
MockMvc в integration-тесте | TS-17 | TestRestTemplate |
| JWT-токен руками в тесте | TS-18 | TestHttpHeaders.* |
Один gigantic @Test-метод 100 строк | TS-3 | один тест = один сценарий |
Имя метода test1, testCancel, testCase2 | TS-16 | <action>_when_<result> |
@DisplayName дублирует имя метода | TS-16 | DisplayName на естественном языке |
| Assert только response, без проверки БД | TS-15 | проверять side-effects (DB, outbox) |
Куда дальше
- Test Strategy → раздел 5. Один тест — нормативные формулировки.
- Базовые правила — AAA, синхронность.
- BaseIntegrationTest —
TestHttpHeaders,@MockitoBean. - DatabasePreparer — Arrange setup.
- TestObjectGenerator — POJO в Arrange.
- Kafka, Redis, async — по умолчанию НЕТ — Outbox проверка в Assert.
- Внешние HTTP — WireMock — стабы.
- Use Case Pattern → спецификация — BR-коды.