---
title: "Условная логика в тесте"
type: "test-smell"
slug: "conditional-test-logic"
url: "http://localhost:3000/ru/test-smells/conditional-test-logic.md"
category: "Запахи неясности"
description: "Тест, который использует `if`/`switch`/тернарные операторы, циклы или `try`/`catch`, чтобы решать, что выполнять или проверять, так что его поведение — и проверяет ли он что-либо вообще — зависит от того, какая ветка выполнится во время работы."
---
# Условная логика в тесте

> Тест, который использует `if`/`switch`/тернарные операторы, циклы или `try`/`catch`, чтобы решать, что выполнять или проверять, так что его поведение — и проверяет ли он что-либо вообще — зависит от того, какая ветка выполнится во время работы.

## Signs and Symptoms

Тест читается как небольшая программа, а не как линейный сценарий «подготовка → действие → проверка». Ищите поток управления в теле теста:

* `if`/`else`, `switch`, тернарные операторы или короткозамкнутые `&&`/`||`, которые управляют тем, какие проверки выполняются.
* Циклы `for`/`while`/`forEach`, которые строят входные данные или перебирают проверки.
* `try`/`catch`, используемый для «тестирования» пути ошибки, с вызовами `expect`, спрятанными внутри `catch`.
* Один тест, переиспользуемый для нескольких случаев через ветвление по флагу или состоянию среды.
* Ожидаемые значения, вычисляемые в тесте (нередко циклом или формулой), вместо захардкоженных.

```js
// Запах: проверки находятся внутри условного/ветвящегося кода
test('user discount', () => {
  const user = getUser();
  if (user.isPremium) {
    expect(price(user)).toBe(80);   // может никогда не выполниться
  } else {
    expect(price(user)).toBe(100);  // может никогда не выполниться
  }
});

test('throws on bad input', () => {
  try {
    parse('!!!');
    // если parse() НЕ выбрасывает, мы проваливаемся вниз и ничего не проверяем → тест проходит
  } catch (err) {
    expect(err.message).toMatch(/invalid/);
  }
});

```

Характерный режим отказа: тест остаётся зелёным даже когда код сломан, потому что ветка, содержащая проверку, ни разу не была выбрана.

## Reasons for the Problem

**Почему это происходит**

* **DRY, доведённый до крайности.** Авторы пытаются покрыть несколько сценариев одним «гибким» тестом, ветвясь по входным данным или флагу, вместо того чтобы писать по тесту на случай (Месарос: _Flexible Test_).
* **Связанность со средой.** Проверяемая система (SUT) не была развязана от своих зависимостей, поэтому тест подстраивается под то состояние, которое находит во время выполнения.
* **Защитная очистка.** `if (resource) resource.close()` закрадывается, чтобы не разрушать фикстуры, которых может не быть (_Complex Teardown_).
* **Вычисляемые ожидания.** Ожидаемый результат выводится тем же алгоритмом, что и продакшен-код, затаскивая эту логику — со всеми циклами — в тест (_Production Logic in Test_).
* **Тестирование пути ошибки вручную.** `try`/`catch` используется для проверки выброшенной ошибки вместо встроенного матчера.

**Почему это вредно**

* **Ложная уверенность (худшее).** Большинство раннеров проваливают тест, только когда проверка выбрасывает исключение. Если проверяющая ветка пропущена — или проверяемый код не выбрасывает исключение внутри `try`, — тест проходит, не проверив _ничего_.
* **Непротестированный код теста.** Ветвления и циклы в тесте — это логика, у которой самой нет тестов; баг в собственном потоке управления теста остаётся незамеченным.
* **Плохая диагностика.** Когда ветвящийся тест падает, сначала нужно выяснить, _какой_ путь выполнился, прежде чем толковать падение.
* **Сниженная читаемость и сопровождаемость.** Линейный тест документирует одно поведение с одним ожидаемым результатом; ветвящийся тест вынуждает читателя моделировать выполнение, чтобы понять, что на самом деле гарантировано.
* **Хрупкость.** Тесты, завязанные на состояние времени выполнения/среды, проходят или падают недетерминированно.

## Treatment

Сделайте каждый тест единственным, безусловным, линейным путём. Конкретно:

1. **Один сценарий на тест.** Разбейте ветвящийся тест на отдельные тесты или используйте управляемый данными API фреймворка (`test.each`, `it.each`, параметризованные тесты), чтобы каждый случай был собственным, ясно названным, отдельно отчитывающимся прогоном.
2. **Вынесите ветвление за пределы теста.** Если случай применим лишь при некотором условии, решайте это на этапе _определения_ (например, `describe`/`it`, выбираемые по конфигурации), а не в теле теста — сами проверки остаются безусловными.
3. **Захардкодьте ожидаемые значения.** Замените вычисляемые ожидания литеральными ожидаемыми результатами (или _Expected Object_ / пользовательским матчером). Не воспроизводите продакшен-логику в тесте.
4. **Тестируйте пути ошибок матчерами,** а не `try`/`catch`: `expect(fn).toThrow(...)`, `await expect(p).rejects.toThrow(...)`. Они громко падают, когда ошибка не выброшена.
5. **Если условная проверка по-настоящему неизбежна,** зафиксируйте их число через `expect.assertions(n)` / `expect.hasAssertions()`, чтобы пропущенная ветка приводила к падению, а не к тихому прохождению.
6. **Замените условную очистку** хуками жизненного цикла фреймворка (`afterEach`) и автоматической/идемпотентной очисткой, чтобы не требовался `if` для защиты очистки.

```js
// До — ветвящийся тест, проверки могут быть пропущены
test('user discount', () => {
  const user = getUser();
  if (user.isPremium) expect(price(user)).toBe(80);
  else                expect(price(user)).toBe(100);
});

// После — один явный случай на строку, каждая проверка всегда выполняется
test.each([
  ['premium', { isPremium: true },  80],
  ['regular', { isPremium: false }, 100],
])('price for %s user', (_label, user, expected) => {
  expect(price(user)).toBe(expected);
});

// До — try/catch, проходящий, когда ничего не выброшено
try { parse('!!!'); } catch (e) { expect(e.message).toMatch(/invalid/); }

// После — падает, если parse() не выбрасывает
expect(() => parse('!!!')).toThrow(/invalid/);

```

## Detected by

- **eslint-jest** `jest/no-conditional-expect` — no-conditional-expect (https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/no-conditional-expect.md)
- **eslint-jest** `jest/no-conditional-in-test` — no-conditional-in-test (https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/no-conditional-in-test.md)
- **eslint-vitest** `vitest/no-conditional-expect` — no-conditional-expect (https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-conditional-expect.md)
- **eslint-vitest** `vitest/no-conditional-in-test` — no-conditional-in-test (https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-conditional-in-test.md)
- **eslint-vitest** `vitest/no-conditional-tests` — no-conditional-tests (https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-conditional-tests.md)
