Skip to main content

🧬 Итерация 6: @Setup, @Teardown, @Context, @Inject — жизненный цикл и shared state

🎯 Цель

Реализовать жизненный цикл для тестов и возможность делиться состоянием между методами и шагами — декларативно и безопасно.


🧠 Проблема

Когда тесты становятся сложнее, появляется необходимость:

  • подготавливать данные (логин, токен, пользователи)
  • очищать ресурсы (удаление, завершение сессии)
  • делиться переменными между методами
  • логировать внутренние действия

В Vitest это решается beforeEach / afterEach и глобальными переменными —
но это не читаемо, не типизировано и не структурировано.


✅ Что добавили

  • @Setup() — вызывается перед каждым тестом
  • @Teardown() — вызывается после каждого теста
  • @Context() — создаёт shared-объект для передачи между методами
  • @Inject() — декларативно подставляет значения в поля из контекста
  • Расширили runTest() для автоматической поддержки всего этого

📦 Пример

@Feature('Сессия')
@Tag('session')
class SessionTests {
@Context()
ctx!: { token?: string; log?: string[] }

@Inject()
token!: string

@Setup()
async init() {
this.ctx.token = 'admin-token'
this.ctx.log = ['Токен создан']
}

@Teardown()
async cleanup() {
this.ctx.log?.push('Очистка контекста')
attach('Лог выполнения', this.ctx.log?.join('\n') ?? '', 'text/plain')
}

@TestCase('Получение токена')
@Description('Проверка, что токен создаётся и доступен через контекст и инжекцию')
async checkToken() {
await step('Проверка токена из @Context', () => {
expect(this.ctx.token).toBe('admin-token')
})

await step('Проверка токена из @Inject', () => {
this.token = this.ctx.token!
expect(this.token).toBe('admin-token')
})
}
}

🔍 Что это даёт

  • Изоляцию логики подготовки/очистки
  • Стандартизованный shared state
  • Возможность вешать логику на @Teardown — даже вложения в отчёт
  • Ясную и декларативную структуру сценариев

🤔 А нельзя ли просто?

Можно. Вот так:

let token: string

beforeEach(() => {
token = 'admin-token'
})

test('тест токена', () => {
expect(token).toBe('admin-token')
})

Работает. Просто. Без магии.

Но когда тестов становится 30+, и каждый — это бизнес-сценарий с шагами, контекстом и вложениями, жизненный цикл превращается в архитектурный элемент — а не в хаотичный beforeEach().


🧩 Наш подход

  • @Setup() = beforeEach() с контекстом
  • @Teardown() = afterEach() + логика
  • @Context() = структурный shared state
  • @Inject() = автоматическое внедрение переменных

Вместо “вызови руками” — “объяви намерение”


🧠 Killer-фичи DSL подхода (vs обычный vitest)

  1. Allure-Ready из коробки (feature, severity, owner, шаги, лейблы)
  2. Автоматический жизненный цикл — setup, teardown, логирование
  3. Модульная архитектура с иерархией Suite → SubSuite
  4. Параметризация сценариев (@TestCase.each)
  5. Тест = Документация (@Description, @Feature)
  6. Расширяемость — auto-labeling, ген генерации, Context, future hooks

🚀 Что дальше?

  • @BeforeAll, @AfterAll — выполнение один раз на класс
  • @Step — шаги как методы