Опирается на правила: TS-15TS-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).
  • @Autowired HTTP-клиент и 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Когда
TestRestTemplateIntegration-тест с полным Spring context + PostgreSQL
MockMvcUnit-тест контроллера без БД (только тестирование сериализации + 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>BaseIntegrationTestTS-15наследование обязательно
@DisplayName без BR-кодаTS-16BR-NNN: ...
MockMvc в integration-тестеTS-17TestRestTemplate
JWT-токен руками в тестеTS-18TestHttpHeaders.*
Один gigantic @Test-метод 100 строкTS-3один тест = один сценарий
Имя метода test1, testCancel, testCase2TS-16<action>_when_<result>
@DisplayName дублирует имя методаTS-16DisplayName на естественном языке
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-коды.