Опирается на правила: TS-9TS-11 из Test Strategy Style Guide → раздел 3. DatabasePreparer.

Важно знать

  • На каждый Bounded Context — свой <Domain>DatabasePreparer как @Component.
  • Три группы методов: clear*(), create*(...), prepare().
  • Не пересоздаём схему между тестами — только DELETE нужных таблиц.
  • Порядок вызовов = порядок исполнения — fluent очередь, не immediate execution.
  • FK ordering: сначала чистим зависимые, последними создаём родительские.
  • DSLContext — единственный механизм. Никаких mock-репозиториев.
  • clearAll() в доменном base через @BeforeEach — чистое состояние.

Setup БД в integration-тесте — обычно самая шумная часть: 30 строк insert-ов вокруг 3 строк бизнес-логики. DatabasePreparer — fluent DSL для setup, делает читаемым. Один paradigm на сервис, никаких произвольных INSERT через JDBC в тестах.

Структура DatabasePreparer

TS-9: @Component per Bounded Context.

@Component
@RequiredArgsConstructor
public class OrderDatabasePreparer {

    private final DSLContext dsl;
    private final List<Runnable> preparers = new ArrayList<>();

    public OrderDatabasePreparer clearOrders() {
        preparers.add(() -> dsl.deleteFrom(ORDERS).execute());
        return this;
    }

    public OrderDatabasePreparer clearOrderItems() {
        preparers.add(() -> dsl.deleteFrom(ORDER_ITEMS).execute());
        return this;
    }

    public OrderDatabasePreparer clearOutbox() {
        preparers.add(() -> dsl.deleteFrom(OUTBOX).execute());
        return this;
    }

    public OrderDatabasePreparer createOrder(OrdersPojo order) {
        preparers.add(() -> dsl.insertInto(ORDERS)
            .set(dsl.newRecord(ORDERS, order))
            .execute());
        return this;
    }

    public OrderDatabasePreparer createOrderItem(OrderItemsPojo item) {
        preparers.add(() -> dsl.insertInto(ORDER_ITEMS)
            .set(dsl.newRecord(ORDER_ITEMS, item))
            .execute());
        return this;
    }

    public void clearAll() {
        clearOrderItems();
        clearOrders();
        clearOutbox();
        prepare();
    }

    public void prepare() {
        preparers.forEach(Runnable::run);
        preparers.clear();
    }
}

Что важно:

  • Fluent — все методы возвращают this, цепочка вызовов читается top-down.
  • Lazy execution — методы добавляют в preparers очередь, не выполняют сразу. prepare() запускает всё.
  • DSLContext — все запросы через JOOQ. Не JdbcTemplate, не EntityManager.
  • POJOs — generated jOOQ POJOs принимаются на вход (OrdersPojo).

Три группы методов

TS-9: clear / create / prepare.

clear*() — очистка

public OrderDatabasePreparer clearOrders() {
    preparers.add(() -> dsl.deleteFrom(ORDERS).execute());
    return this;
}

Чистит конкретную таблицу. clearAll() — convenience method, чистит всё в правильном FK-порядке.

create*(...) — вставка

public OrderDatabasePreparer createOrder(OrdersPojo order) {
    preparers.add(() -> dsl.insertInto(ORDERS)
        .set(dsl.newRecord(ORDERS, order))
        .execute());
    return this;
}

Принимает POJO, делает INSERT. POJO создаётся через TestObjectGenerator (см. TestObjectGenerator).

prepare() — запуск

public void prepare() {
    preparers.forEach(Runnable::run);
    preparers.clear();
}

Выполняет всю очередь, очищает.

Не пересоздаём схему

TS-10: только DELETE, не DROP TABLE.

// КАТАСТРОФА — медленно
@BeforeEach
void setUp() {
    Liquibase.recreate();  // 5 секунд
}

// ХОРОШО — миллисекунды
@BeforeEach
void setUp() {
    databasePreparer.clearAll();
}

Различия:

  • Liquibase recreate — drop all tables + apply all migrations = 3-10 секунд на каждый тест.
  • DELETE — пустые таблицы за миллисекунды.

Liquibase — только раз при старте контейнера (применяет migrations при первом тесте). Дальше — DELETE через DatabasePreparer.

FK ordering

TS-11: порядок методов = порядок исполнения.

// Если есть FK orders.id ← order_items.order_id

// КАТАСТРОФА — FK violation
databasePreparer
    .clearOrders()       // ошибка: order_items ссылаются
    .clearOrderItems()
    .prepare();

// ХОРОШО — сначала зависимые
databasePreparer
    .clearOrderItems()
    .clearOrders()
    .prepare();

При создании — наоборот:

// Если есть FK orders.id ← order_items.order_id

// КАТАСТРОФА — FK violation
databasePreparer
    .createOrderItem(item)  // ошибка: order_id не существует
    .createOrder(order)
    .prepare();

// ХОРОШО — сначала родителя
databasePreparer
    .createOrder(order)
    .createOrderItem(item)
    .prepare();

Правило: clear от leaf к root, create от root к leaf.

clearAll в @BeforeEach

В доменном base (см. BaseIntegrationTest):

public abstract class OrderBaseIntegrationTest extends PlatformBaseIntegrationTest {

    @Autowired
    protected OrderDatabasePreparer databasePreparer;

    @BeforeEach
    void clearDatabase() {
        databasePreparer.clearAll();
    }
}

Это гарантирует чистое состояние перед каждым тестом. В тестах — только дополнительный setup:

@Test
void confirmOrder_whenDraft_returns200() {
    var draft = new OrderTestObjectGenerator()
        .withStatus(OrderStatus.DRAFT)
        .generate();
    databasePreparer.createOrder(draft).prepare();

    // act + assert
}

Что запрещено

АнтипаттернПравилоЧто взамен
JdbcTemplate.execute("INSERT INTO ...") в тестеTS-9DatabasePreparer через DSLContext
Mock OrderRepository в integration-тестеTS-9реальный DSLContext, реальная БД
Liquibase recreate перед каждым тестомTS-10DELETE через DatabasePreparer
@Transactional rollback вместо clearAll()TS-10explicit DELETE; rollback скрывает FK violations
FK ordering нарушенTS-11clear leaf-first, create root-first
DatabasePreparer одна для всего сервисаTS-9per Bounded Context
Inline dsl.deleteFrom(...) в тестахTS-9методы Preparer-а
prepare() не вызван — preparers не выполненыTS-9.prepare() в конце цепочки

Куда дальше

  • Test Strategy → раздел 3. DatabasePreparer — нормативные формулировки.
  • BaseIntegrationTest — @Autowired DatabasePreparer в доменном base.
  • TestObjectGenerator — что передавать в createOrder(...).
  • Один тест — пример использования.
  • JOOQ → repository — generated OrdersPojo, DSLContext.
  • PG schema — FK structure.