Matchers (Проверки)
Matchers — функции сравнения в Jest, которые проверяют, что результат теста соответствует ожиданию.
Зачем нужно
Matchers — язык утверждений в тестах. toBe, toEqual, toThrow — каждый matcher проверяет свой аспект. Правильный выбор matcher делает тест читаемым и даёт понятные сообщения об ошибках.
Где используется
В каждом тесте Jest/Vitest. Строка expect(value).matcher — основа любого теста.
Предпосылки
Настройка Jest, Основы тестирования
Базовые matchers
toBe — строгое равенство (===)
test('toBe — примитивы', () => {
expect(2 + 2).toBe(4);
expect('hello').toBe('hello');
expect(true).toBe(true);
expect(null).toBe(null);
expect(undefined).toBe(undefined);
});
// ОСТОРОЖНО: для объектов toBe проверяет ссылку!
test('toBe НЕ работает для объектов', () => {
const a = { x: 1 };
const b = { x: 1 };
expect(a).not.toBe(b); // Разные ссылки!
expect(a).toBe(a); // Одна ссылка — ОК
});
toEqual — глубокое сравнение
test('toEqual — объекты и массивы', () => {
expect({ a: 1, b: 2 }).toEqual({ a: 1, b: 2 });
expect([1, 2, 3]).toEqual([1, 2, 3]);
// Вложенные объекты
expect({
user: { name: 'Алиса', scores: [10, 20] },
}).toEqual({
user: { name: 'Алиса', scores: [10, 20] },
});
});
// toStrictEqual — строже: проверяет undefined свойства и тип
test('toStrictEqual vs toEqual', () => {
expect({ a: 1, b: undefined }).toEqual({ a: 1 }); // PASS
expect({ a: 1, b: undefined }).not.toStrictEqual({ a: 1 }); // разные!
});
Truthiness — проверки на «правдивость»
test('truthiness matchers', () => {
expect(null).toBeNull();
expect(undefined).toBeUndefined();
expect('hello').toBeDefined();
expect(true).toBeTruthy();
expect(1).toBeTruthy();
expect('text').toBeTruthy();
expect(false).toBeFalsy();
expect(0).toBeFalsy();
expect('').toBeFalsy();
expect(null).toBeFalsy();
expect(undefined).toBeFalsy();
});
Числа
test('числовые matchers', () => {
expect(10).toBeGreaterThan(5);
expect(10).toBeGreaterThanOrEqual(10);
expect(5).toBeLessThan(10);
expect(5).toBeLessThanOrEqual(5);
// Для дробных чисел — НЕ используй toBe!
expect(0.1 + 0.2).not.toBe(0.3); // Ошибка IEEE 754
expect(0.1 + 0.2).toBeCloseTo(0.3, 5); // OK, 5 знаков
// NaN
expect(NaN).toBeNaN();
// Infinity
expect(1 / 0).toBe(Infinity);
});
Строки
test('строковые matchers', () => {
expect('Hello World').toContain('World');
expect('Hello World').toMatch(/hello/i); // RegExp
expect('user@email.com').toMatch(
/^[\w.-]+@[\w.-]+\.\w{2,}$/
);
// Длина
expect('abc').toHaveLength(3);
});
Массивы и итерируемые объекты
test('массивы', () => {
const fruits = ['яблоко', 'банан', 'вишня'];
expect(fruits).toContain('банан');
expect(fruits).toHaveLength(3);
expect(fruits).toEqual(expect.arrayContaining(['вишня', 'яблоко']));
// Не содержит
expect(fruits).not.toContain('груша');
// Содержит объект
const users = [
{ id: 1, name: 'Алиса' },
{ id: 2, name: 'Боб' },
];
expect(users).toContainEqual({ id: 1, name: 'Алиса' });
});
Объекты
test('объекты', () => {
const user = {
id: 1,
name: 'Алиса',
email: 'alice@test.com',
address: { city: 'Москва' },
};
// Проверка свойства
expect(user).toHaveProperty('name');
expect(user).toHaveProperty('name', 'Алиса');
expect(user).toHaveProperty('address.city', 'Москва');
// Частичное совпадение
expect(user).toMatchObject({
name: 'Алиса',
email: 'alice@test.com',
});
// expect.objectContaining — внутри других matchers
expect(user).toEqual(expect.objectContaining({
name: expect.any(String),
id: expect.any(Number),
}));
});
Исключения
test('исключения', () => {
function divide(a, b) {
if (b === 0) throw new Error('Деление на ноль');
return a / b;
}
// Обязательно оборачиваем в функцию!
expect( => divide(10, 0)).toThrow;
expect( => divide(10, 0)).toThrow('Деление на ноль');
expect( => divide(10, 0)).toThrow(/ноль/);
expect( => divide(10, 0)).toThrow(Error);
// Не выбрасывает
expect( => divide(10, 2)).not.toThrow;
});
Функции-моки
test('проверка вызовов мок-функций', () => {
const callback = jest.fn;
callback('a');
callback('b', 'c');
callback('a');
// Был ли вызван
expect(callback).toHaveBeenCalled();
expect(callback).toHaveBeenCalledTimes(3);
// С какими аргументами
expect(callback).toHaveBeenCalledWith('a');
expect(callback).toHaveBeenCalledWith('b', 'c');
// Последний вызов
expect(callback).toHaveBeenLastCalledWith('a');
// N-й вызов (0-indexed)
expect(callback).toHaveBeenNthCalledWith(1, 'a');
expect(callback).toHaveBeenNthCalledWith(2, 'b', 'c');
// Вернул значение (если mockReturnValue задан)
const fn = jest.fn.mockReturnValue(42);
fn;
expect(fn).toHaveReturnedWith(42);
});
Snapshot matchers
test('snapshot', () => {
const user = createUser('Алиса', 25);
// Полный снапшот (сохраняется в .snap файл)
expect(user).toMatchSnapshot;
// Inline snapshot (встраивается прямо в тест)
expect(user).toMatchInlineSnapshot(`
{
"age": 25,
"id": Any<Number>,
"name": "Алиса",
}
`);
});
Модификатор .not
test('.not инвертирует проверку', () => {
expect(5).not.toBe(3);
expect('hello').not.toContain('xyz');
expect([1, 2]).not.toHaveLength(3);
expect({ a: 1 }).not.toHaveProperty('b');
expect(() => {}).not.toThrow;
});
Асимметричные matchers (expect.xxx)
test('expect.any и expect.anything', () => {
expect({ id: 42, created: new Date }).toEqual({
id: expect.any(Number),
created: expect.any(Date),
});
// anything — что угодно кроме null/undefined
expect({ key: 'value' }).toEqual({
key: expect.anything,
});
// stringContaining
expect({ msg: 'Ошибка: файл не найден' }).toEqual({
msg: expect.stringContaining('файл не найден'),
});
// stringMatching — с RegExp
expect({ email: 'test@mail.com' }).toEqual({
email: expect.stringMatching(/@/),
});
});
Кастомные matchers
// Расширяем expect своими проверками
expect.extend({
toBeWithinRange(received, floor, ceiling) {
const pass = received >= floor && received <= ceiling;
if (pass) {
return {
message: => `ожидалось, что ${received} НЕ будет в диапазоне ${floor}-${ceiling}`,
pass: true,
};
} else {
return {
message: => `ожидалось, что ${received} будет в диапазоне ${floor}-${ceiling}`,
pass: false,
};
}
},
});
test('кастомный matcher', () => {
expect(50).toBeWithinRange(1, 100);
expect(150).not.toBeWithinRange(1, 100);
});
Шпаргалка: выбор matcher
| Что проверяю | Matcher |
|---|---|
| Примитив (число, строка, boolean) | toBe |
| Объект / массив | toEqual / toStrictEqual |
| Дробное число | toBeCloseTo |
| Содержит элемент | toContain / toContainEqual |
| Строка по паттерну | toMatch |
| Свойство объекта | toHaveProperty |
| Выбрасывает ошибку | toThrow |
| null / undefined | toBeNull / toBeUndefined |
| Мок вызван | toHaveBeenCalledWith |
| Снапшот | toMatchSnapshot |
Частые ошибки
1. toBe вместо toEqual для объектов
// ПЛОХО:
expect({ a: 1 }).toBe({ a: 1 }); // FAIL — разные ссылки
// ХОРОШО:
expect({ a: 1 }).toEqual({ a: 1 }); // PASS
2. toThrow без оборачивающей функции
// ПЛОХО: ошибка выбросится ДО expect
expect(divide(1, 0)).toThrow; // TypeError!
// ХОРОШО: оборачиваем в стрелочную функцию
expect( => divide(1, 0)).toThrow;
3. Сравнение дробных чисел через toBe
// ПЛОХО:
expect(0.1 + 0.2).toBe(0.3); // FAIL (0.30000000000000004)
// ХОРОШО:
expect(0.1 + 0.2).toBeCloseTo(0.3);
Практика
- Напиши тесты для функции
findMax(arr): пустой массив, один элемент, отрицательные числа, дубликаты - Протестируй функцию
parseUrl(str): проверь каждое свойство черезtoHaveProperty - Создай кастомный matcher
toBeValidEmailи используй его в тестах - Используй
expect.objectContainingдля проверки частичного совпадения API-ответа