Снимок - это зафиксированный слепок значения. При первом прогоне toMatchSnapshot сериализует то, что вы ему передали, и сохраняет результат в файл .snap рядом с тестом. На следующих прогонах Jest сериализует значение заново и сравнивает с сохраненным: совпало - тест зелен, разошлось - красен и показывает разницу. Инструмент не проверяет, что значение правильное - он проверяет, что оно не изменилось с прошлого раза. Это фиксация факта, а не суждение о правильности.
Отсюда соблазн наивного применения: снимать все подряд. Отрендерил компонент - сделай снимок всего DOM. Получил ответ ручки - снимок всего тела. Кажется, что это бесплатное покрытие: одна строчка, а под контролем целая страница. Строчка действительно одна, а вот контроль - иллюзия.
Ломается это на ревью. Снимок всего DOM - это сотни строк разметки, которые никто не читает. Когда такой снимок краснеет, разработчик видит гигантский diff, где перемешаны осмысленное изменение и десяток случайных, пожимает плечами и запускает jest -u не глядя. Снимок превращается в резиновый штамп: он больше не фиксирует контракт, он фиксирует последнее, что случайно оказалось на экране. Это и есть свалка DOM - объем без смысла.
Переосмысление простое: снимок - это reviewable contract, читаемый контракт. Он должен быть маленьким, сериализуемым и понятным человеку, который смотрит на него первый раз. Снимайте не весь мир, а узкое значение: публичный DTO, результат чистой функции, текст ошибки. Для совсем коротких значений есть toMatchInlineSnapshot - он хранит ожидаемое прямо в теле теста, а не в отдельном .snap. Тогда diff на ревью виден в том же файле, что и код, и обмануть себя невозможно.
Отдельная ловушка - сгенерированные поля. Если в объекте есть id или дата создания, снимок будет краснеть на каждом прогоне, потому что эти значения меняются сами по себе, а не из-за вашего кода. Ответ - property matchers: асимметричный матчер (expect.any(String), expect.any(Date)) для конкретного поля. Jest проверяет такое поле матчером, а в снимок пишет сам матчер вместо изменчивого значения. Снимок остается стабильным и краснеет только на настоящем изменении структуры.
И самое главное - дисциплина ключа -u. Команда jest -u перезаписывает снимки текущими значениями. Это не автопочинка и не способ сделать красное зеленым. Это акт принятия нового поведения: вы посмотрели на diff, убедились, что изменение задумано, и осознанно зафиксировали новую норму. Ключ -u без взгляда на diff - это отказ от единственного, ради чего снимок существует.
Цена и польза уравновешиваются размером. Маленький читаемый снимок дешев в поддержке и ловит непреднамеренные изменения формы данных - он оправдан для сериализаторов, стабильных выводов, сообщений об ошибках. Большой снимок дорог и бесполезен: его никто не ревьюит, и он деградирует до штампа. Классический провал выглядит так - огромный снимок плюс рефлекс jest -u на каждое падение. Тест всегда зеленый после обновления, красный diff никто не читает, и то, что снимок якобы охраняет, на деле не охраняется ничем.
expect(container).toMatchSnapshot()expect(toPublicDto(user))
.toMatchInlineSnapshot(`
{
"role": "editor",
}
`)expect(user).toMatchSnapshot({
id: expect.any(String),
createdAt: expect.any(Date),
})