---
title: "Чувствительное равенство"
type: "test-smell"
slug: "sensitive-equality"
url: "http://localhost:3000/ru/test-smells/sensitive-equality.md"
category: "Запахи утверждений"
description: "Тест проверяет поведение, сравнивая строковое представление объекта (toString(), JSON.stringify(), отрендеренный HTML) с ожидаемым строковым литералом, тем самым привязываясь к несущественным деталям форматирования вместо действительно важных значений."
---
# Чувствительное равенство

> Тест проверяет поведение, сравнивая строковое представление объекта (toString(), JSON.stringify(), отрендеренный HTML) с ожидаемым строковым литералом, тем самым привязываясь к несущественным деталям форматирования вместо действительно важных значений.

## Signs and Symptoms

Чувствительное равенство узнаётся, когда _фактическая_ сторона проверки сериализует объект в текст, а _ожидаемая_ сторона — строковый литерал, в котором множество полей слеплены вместе разделителями, кавычками, скобками и пробелами.

Характерные признаки:

* Фактическое значение получено из `.toString()`, `String(x)`, шаблонного литерала, `JSON.stringify(...)`, `wrapper.html()`, `el.outerHTML` или снапшота сериализованного кома.
* Ожидаемое значение — длинная строка-литерал (нередко вставленная из предыдущего упавшего прогона), и с первого взгляда не понять, _какая_ её часть и есть проверяемое.
* Тест ломается от изменений, не влияющих на поведение: переставленные ключи, добавленные/убранные пробелы, точность чисел, формат даты, локаль, часовой пояс или порядок обхода `Map`/`Set`/объекта.

```js
// Сравнение сериализованной формы со строковым литералом
expect(JSON.stringify(user)).toBe('{"name":"Bob","age":30}');

// Проверка вывода toString()
expect(order.toString()).toBe('Order[id=7, total=42.00, items=2]');

// Сравнение полного отрендеренного HTML с литералом
expect(wrapper.html()).toBe('<div class="card"><h2>Bob</h2><span>30</span></div>');

// Сравнение строк, чувствительное к локали/часовому поясу
expect(d.toString()).toBe('Mon Jun 15 2026 00:00:00 GMT+0000 (UTC)');

```

## Reasons for the Problem

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

* Это быстро и легко. Как отмечают ван Дёрсен и др. в _Refactoring Test Code_, быстро вычислить фактический результат, превратить его в строку и сравнить со строковым литералом, представляющим ожидаемое значение, — причём литерал нередко просто скопирован из упавшего прогона. Инструменты снапшотов делают это ещё соблазнительнее.
* У объекта не настроено осмысленное равенство (`equals`/глубокий матчер), либо граф объекта велик, поэтому сбросить его в строку _кажется_ проще, чем проверять поле за полем.

**Чем это вредит**

* **Хрупкость / ложные падения.** Строка несёт массу несущественных деталей — запятые, кавычки, пробелы, порядок ключей, точность чисел с плавающей точкой, локаль, часовой пояс, порядок обхода коллекций. Стоит измениться формату `toString()`/сериализации, как падать начинают никак не связанные тесты, хотя поведение корректно. Это классическая причина [хрупкого теста](http://xunitpatterns.com/Fragile%20Test.html).
* **Сопровождаемость.** Единственное изменение форматирования или библиотеки вынуждает править множество тестов, а гигантский ожидаемый литерал утомительно обновлять и легко испортить незаметной ошибкой.
* **Читаемость / скрытое намерение.** Ожидаемое значение в виде стены текста скрывает, что именно проверяется; ревьюер не видит того единственного поля, которое важно тесту.
* **Ложная уверенность.** Строковый ком может пройти по неверным причинам (два разных состояния сериализуются в один и тот же текст) и подталкивает разработчиков перезаписывать литерал/снапшот при падении, не проверяя, что новое значение действительно корректно.
* **Флакование.** Чувствительные к локали, часовому поясу и порядку строки ставят исход в зависимость от окружения, в котором выполняется тест.

## Treatment

Проверяйте **данные**, а не их отображение.

1. **Сравнивайте структурированные значения глубоким равенством** вместо строкового (`toEqual`/`toStrictEqual`), которое для объектов не зависит от порядка и пробелов.
2. **Проверяйте только значимые атрибуты** частичными матчерами (`toMatchObject`, `expect.objectContaining`), чтобы изменение форматирования в другом месте не могло сломать тест.
3. **Разбирайте, а не сопоставляйте строки.** Получив сериализованную полезную нагрузку (например, JSON-тело API), разберите её обратно в структуру и сравнивайте структуры.
4. **Введите метод равенства/сравнения** у объекта (рефакторинг, который рекомендуют ван Дёрсен и др.) и проверяйте равенство объектов, а не равенство `toString()`.
5. **Если без сравнения сериализованного вывода не обойтись, сначала канонизируйте** — отсортируйте ключи, зафиксируйте локаль/часовой пояс, задайте точность чисел — и предпочтите проверенный снапшот вручную вставленному встроенному литералу.
6. **Для тестов DOM/компонентов делайте семантические запросы** (Testing Library `getByRole`, `toHaveTextContent`) вместо сравнения всего HTML.

```js
// До — чувствительное равенство
expect(JSON.stringify(user)).toBe('{"name":"Bob","age":30}');

// После — структурное равенство (не зависит от порядка ключей / пробелов)
expect(user).toEqual({ name: 'Bob', age: 30 });

// После — проверяем только нужное поле
expect(user).toMatchObject({ age: 30 });

```

```js
// До — строковое сравнение полезной нагрузки API
expect(res.text).toBe('{"id":7,"total":42}');

// После — сравниваем разобранные структуры
expect(JSON.parse(res.text)).toEqual({ id: 7, total: 42 });

```
