7. Matchers и Assertions

Стандартные Matchers
Заголовок раздела «Стандартные Matchers»// ─── Примитивы ───────────────────────────────────────────expect(42).toBe(42); // строгое равенство (===)expect(42).not.toBe(43);expect('hello').toBe('hello');
// ─── Объекты и массивы ───────────────────────────────────expect({ a: 1, b: 2 }).toEqual({ a: 1, b: 2 }); // глубокое равенствоexpect([1, 2, 3]).toEqual([1, 2, 3]);
// ─── Null / Undefined / Boolean ──────────────────────────expect(null).toBeNull();expect(undefined).toBeUndefined();expect('hello').toBeDefined();expect(true).toBeTruthy();expect(0).toBeFalsy();expect('').toBeFalsy();expect([]).toBeTruthy(); // ВНИМАНИЕ: пустой массив — truthy!
// ─── Числа ───────────────────────────────────────────────expect(10).toBeGreaterThan(5);expect(10).toBeGreaterThanOrEqual(10);expect(5).toBeLessThan(10);expect(5).toBeLessThanOrEqual(5);expect(0.1 + 0.2).toBeCloseTo(0.3, 5); // для float!
// ─── Строки ──────────────────────────────────────────────expect('hello world').toContain('world');expect('hello').toMatch(/^hel/);expect('hello').toMatch('ell');expect('hello world').toHaveLength(11);
// ─── Массивы ─────────────────────────────────────────────expect([1, 2, 3]).toContain(2);expect([1, 2, 3]).toHaveLength(3);expect([1, 2, 3]).toContainEqual({ id: 1 }); // глубокое в массиве
// ─── Объекты ─────────────────────────────────────────────expect({ name: 'Alice', age: 28 }).toMatchObject({ name: 'Alice' });expect({ id: 1, name: 'Alice' }).toHaveProperty('name');expect({ id: 1, name: 'Alice' }).toHaveProperty('name', 'Alice');expect({ a: { b: 2 } }).toHaveProperty('a.b', 2);
// ─── Ошибки ──────────────────────────────────────────────expect(() => JSON.parse('{invalid}')).toThrow();expect(() => JSON.parse('{invalid}')).toThrow(SyntaxError);expect(() => { throw new Error('Oops') }).toThrow('Oops');expect(() => { throw new Error('Oops') }).toThrow(/oops/i);Asymmetric Matchers
Заголовок раздела «Asymmetric Matchers»Используются когда точное значение неизвестно заранее.
// expect.any() — любое значение типаexpect({ id: 42, name: 'Alice' }).toMatchObject({ id: expect.any(Number), name: expect.any(String),});
// expect.anything() — любое не-null значениеexpect({ token: 'abc123', expires: new Date() }).toMatchObject({ token: expect.anything(), expires: expect.anything(),});
// expect.arrayContaining() — массив содержит элементыexpect([1, 2, 3, 4]).toEqual(expect.arrayContaining([2, 4]));expect(['alice', 'bob', 'carol']).toEqual( expect.arrayContaining(['alice', 'carol']));
// expect.objectContaining() — объект содержит поляexpect({ id: 1, name: 'Alice', createdAt: new Date(),}).toMatchObject( expect.objectContaining({ id: expect.any(Number), name: 'Alice', }));
// expect.stringContaining() / stringMatching()expect('Hello, World!').toEqual(expect.stringContaining('World'));Custom Matchers
Заголовок раздела «Custom Matchers»// Расширяем expect собственными проверкамиexpect.extend({ toBeValidEmail(received) { const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const pass = re.test(received); return { message: () => pass ? `Expected ${received} not to be a valid email` : `Expected ${received} to be a valid email`, pass, }; },
toBeWithinRange(received, min, max) { const pass = received >= min && received <= max; return { message: () => `Expected ${received} to ${pass ? 'not ' : ''}be within [${min}, ${max}]`, pass, }; },});
// TypeScript (глобальная декларация)declare global { namespace jest { interface Matchers<R> { toBeValidEmail(): R; toBeWithinRange(min: number, max: number): R; } }}
// Использованиеtest('custom matchers', () => { expect('notanemail').not.toBeValidEmail(); expect(5).toBeWithinRange(1, 10);});Soft Assertions
Заголовок раздела «Soft Assertions»// Стандартно: тест останавливается на первой ошибкеtest('all fields valid', () => { const user = createUser(); expect(user.id).toBeDefined(); // если падает здесь — остальное не проверяется expect(user.name).toBe('Alice'); expect(user.email).toBeValidEmail();});
// Soft assertions: проверяем все и сообщаем все ошибкиtest('all fields valid (soft)', () => { const user = createUser(); const errors = [];
try { expect(user.id).toBeDefined(); } catch(e) { errors.push(e.message); } try { expect(user.name).toBe('Alice'); } catch(e) { errors.push(e.message); } try { expect(user.email).toBeValidEmail(); } catch(e) { errors.push(e.message); }
if (errors.length) throw new Error(errors.join('\n'));});@jest/expect-utils для сравнений
Заголовок раздела «@jest/expect-utils для сравнений»import { equals, subsetEquality } from '@jest/expect-utils';
// Глубокое равенство программноconst isEqual = equals({ a: 1 }, { a: 1 }); // trueПрактические задания
Заголовок раздела «Практические задания»- Создай custom matcher
toBePositive()иtoBeNegative() - Используй asymmetric matchers для тестирования API ответа с динамическим ID
- Напиши тест с
test.eachдля проверки набора email адресов
- toBe — строгое равенство, toEqual — глубокое
- toMatchObject — частичное совпадение объекта
- Asymmetric matchers — для динамических значений
- Custom matchers — расширяем expect под свою бизнес-логику