Опирается на правила:
TS-9…TS-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-9 | DatabasePreparer через DSLContext |
Mock OrderRepository в integration-тесте | TS-9 | реальный DSLContext, реальная БД |
| Liquibase recreate перед каждым тестом | TS-10 | DELETE через DatabasePreparer |
@Transactional rollback вместо clearAll() | TS-10 | explicit DELETE; rollback скрывает FK violations |
| FK ordering нарушен | TS-11 | clear leaf-first, create root-first |
DatabasePreparer одна для всего сервиса | TS-9 | per 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.