Using Matchers

Overview

Matchers are the heart of Fauji's assertion system. They allow you to test values in expressive, readable ways. Fauji provides 40+ built-in matchers:

  • toBe() - Strict equality
  • toEqual() - Deep equality
  • toBeCloseTo() - Number closeness
  • toSatisfy() - Custom predicate
  • toBeArray() - Array type
  • toBeBoolean() - Boolean type
  • toBeDate() - Date type
  • toBeFunction() - Function type
  • toBeInstanceOf() - Instance checking
  • toBeNumber() - Number type
  • toBeObject() - Object type
  • toBeRegExp() - RegExp type
  • toBeString() - String type
  • toBeNull() - Null check
  • toBeUndefined() - Undefined check
  • toBeDefined() - Defined check
  • toBeGreaterThan() - Greater than
  • toBeGreaterThanOrEqual() - Greater than or equal
  • toBeLessThan() - Less than
  • toBeLessThanOrEqual() - Less than or equal
  • toMatch() - String/regex matching
  • toContain() - Array/string contains
  • toMatchObject() - Object partial matching
  • toHaveProperty() - Property existence
  • toMatchSchema() - Schema validation
  • toHaveLength() - Array/string length
  • toBeEmpty() - Empty check
  • toThrow() - Function throws error
  • toBeValidEmail() - Email validation
  • toBeValidJSON() - JSON validation
  • toBeValidURL() - URL validation
  • toBeValidUUID() - UUID validation
  • toResolve() - Promise resolution
  • toReject() - Promise rejection
  • toHaveBeenCalled() - Function called
  • toHaveBeenCalledTimes() - Call count
  • toHaveBeenCalledWith() - Called with args
  • toHaveBeenCalledWithExactly() - Exact args
  • toHaveBeenLastCalledWith() - Last call args
  • toHaveBeenNthCalledWith() - Nth call args
  • toHaveReturned() - Function returned
  • toHaveReturnedWith() - Returned value
  • toHaveLastReturnedWith() - Last return value
  • toHaveNthReturnedWith() - Nth return value
  • toHaveThrown() - Function threw
  • toHaveThrownWith() - Threw specific error

toBe(expected)

Tests strict equality using Object.is(). Use for primitives and reference equality.


describe('toBe Examples', () => {
  test('primitive values', () => {
    expect(42).toBe(42);
    expect('hello').toBe('hello');
    expect(true).toBe(true);
    expect(null).toBe(null);
    expect(undefined).toBe(undefined);
  });

  test('handles special values', () => {
    expect(NaN).toBe(NaN); // Object.is handles NaN correctly
    expect(-0).not.toBe(+0); // Distinguishes -0 from +0
  });

  test('reference equality', () => {
    const obj = { a: 1 };
    const same = obj;
    expect(same).toBe(obj); // Same reference
    expect({ a: 1 }).not.toBe({ a: 1 }); // Different objects
  });
});

toEqual(expected)

Tests deep equality. Recursively compares objects and arrays.


describe('toEqual Examples', () => {
test('deep object equality', () => {
  expect({ a: 1, b: 2 }).toEqual({ a: 1, b: 2 });
  expect({
    user: { name: 'John', age: 30 },
    settings: { theme: 'dark' }
  }).toEqual({
    user: { name: 'John', age: 30 },
    settings: { theme: 'dark' }
  });
});

test('array equality', () => {
  expect([1, 2, 3]).toEqual([1, 2, 3]);
  expect([{ a: 1 }, { b: 2 }]).toEqual([{ a: 1 }, { b: 2 }]);
});

test('mixed nested structures', () => {
  expect({
    users: [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }],
    meta: { total: 2, page: 1 }
  }).toEqual({
    users: [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }],
    meta: { total: 2, page: 1 }
  });
});
});

toBeCloseTo(expected, precision?)

Tests numerical closeness for floating-point comparisons. Default precision is 2 decimal places.


describe('toBeCloseTo Examples', () => {
test('floating point precision', () => {
  expect(0.1 + 0.2).toBeCloseTo(0.3); // Default precision (2)
  expect(0.1 + 0.2).toBeCloseTo(0.3, 2);
  expect(Math.PI).toBeCloseTo(3.14159, 5);
});

test('handles edge cases', () => {
  expect(NaN).toBeCloseTo(NaN); // NaN equals NaN
  expect(Infinity).toBeCloseTo(Infinity);
  expect(-Infinity).toBeCloseTo(-Infinity);
});

test('precision parameter', () => {
  expect(1.23456).toBeCloseTo(1.23, 2); // Passes
  expect(1.23456).not.toBeCloseTo(1.23, 4); // Fails (too precise)
});
});

toSatisfy(predicate)

Tests if a value satisfies a custom predicate function.


describe('toSatisfy Examples', () => {
  test('custom predicate', () => {
    expect(10).toSatisfy(x => x > 5);
  });
});

toBeArray()

Tests if a value is an array.


describe('toBeArray Examples', () => {
  test('array checks', () => {
    expect([1, 2, 3]).toBeArray();
    expect([]).toBeArray();
    expect(new Array(5)).toBeArray();
  });
});

toBeBoolean()

Tests if a value is a boolean.


describe('toBeBoolean Examples', () => {
  test('boolean checks', () => {
    expect(true).toBeBoolean();
  });
});

toBeDate()

Tests if a value is a date.


describe('toBeDate Examples', () => {
  test('date checks', () => {
    expect(new Date()).toBeDate();
    expect(new Date('2021-01-01')).toBeDate();
    expect(new Date('invalid date')).not.toBeDate();
  });
});

toBeFunction()

Tests if a value is a function.


describe('toBeFunction Examples', () => {
  test('function checks', () => {
    expect(() => {}).toBeFunction();
    expect(function() {}).toBeFunction();
    expect(new Function('return 1')).toBeFunction();
  });
});

toBeInstanceOf()

Tests if a value is an instance of a class.


describe('Type Checking Matchers', () => {
  test('complex type checking', () => {
    class User {
      constructor(name) { this.name = name; }
    }
    const user = new User('John');

    expect(user).toBeInstanceOf(User);
    expect(user).toBeInstanceOf(Object);
    expect(user).not.toBeInstanceOf(Array);
  });
});

toBeNumber()

Tests if a value is a number.


describe('toBeNumber Examples', () => {
  test('number checks', () => {
    expect(42).toBeNumber();
    expect(new Number(42)).toBeNumber();
    expect(Number.NaN).not.toBeNumber();
  });
});

toBeObject()

Tests if a value is an object.


describe('toBeObject Examples', () => {
  test('object checking', () => {
    class User {
      constructor(name) { this.name = name; }
    }
    const user = new User('John');
  
    expect(user).toBeObject();
  });

  test('object type checking', () => {
    expect({}).toBeObject();
    expect([]).not.toBeObject();
    expect(null).not.toBeObject(); // null is not considered an object
  });
});

toBeRegExp()

Tests if a value is a regular expression.


describe('toBeRegExp Examples', () => {
  test('regex checks', () => {
    expect(/pattern/).toBeRegExp();
    expect(new RegExp('pattern')).toBeRegExp();
  });
});

toBeString()

Tests if a value is a string.


describe('toBeString Examples', () => {
  test('string checks', () => {
    expect('hello').toBeString();
    expect(new String('hello')).toBeString();
  });
});

toBeNull()

Tests if a value is null.


describe('Nullish Matchers', () => {
  test('null checks', () => {
    expect(null).toBeNull();
    expect(undefined).not.toBeNull();
    expect(0).not.toBeNull();
    expect('').not.toBeNull();
  });
});

toBeUndefined()


describe('Undefined Matcher', () => {
  test('undefined checks', () => {
    expect(undefined).toBeUndefined();
    expect(null).not.toBeUndefined();
    expect(0).not.toBeUndefined();
  });
});

toBeDefined()


describe('Defined Matcher', () => {
  test('defined checks', () => {
    expect('value').toBeDefined();
    expect(0).toBeDefined();
    expect(false).toBeDefined();
    expect(null).toBeDefined(); // null is defined
    expect(undefined).not.toBeDefined();
  });
});

toBeGreaterThan()


describe('Numerical Comparison Matchers', () => {
  test('greater than comparisons', () => {
    expect(10).toBeGreaterThan(5);
  });
});

toBeGreaterThanOrEqual()


describe('Numerical Comparison Matchers', () => {
  test('greater than or equal comparisons', () => {
    expect(10).toBeGreaterThanOrEqual(10);
    expect(10).toBeGreaterThanOrEqual(5);
  });
});

toBeLessThan()


describe('Numerical Comparison Matchers', () => {
  test('less than comparisons', () => {
    expect(5).toBeLessThan(10);
  });
});

toBeLessThanOrEqual()


describe('Numerical Comparison Matchers', () => {
  test('less than or equal comparisons', () => {
    expect(5).toBeLessThanOrEqual(5);
    expect(5).toBeLessThanOrEqual(10);
  });
});

toMatch(pattern)

Tests strings against patterns (strings or regular expressions).


describe('toMatch Examples', () => {
  test('string matching', () => {
    expect('Hello World').toMatch('World');
    expect('Hello World').toMatch('Hello');
    expect('Hello World').not.toMatch('Goodbye');
  });

  test('regex matching', () => {
    expect('user@example.com').toMatch(/\w+@\w+\.\w+/);
    expect('123-456-7890').toMatch(/^\d{3}-\d{3}-\d{4}$/);
    expect('JavaScript').toMatch(/^[A-Z]/); // Starts with capital
  });

  test('case sensitivity', () => {
    expect('Hello').toMatch('Hello');
    expect('Hello').not.toMatch('hello'); // Case sensitive
    expect('Hello').toMatch(/hello/i); // Case insensitive regex
  });
});

toContain(item)

Tests if arrays contain items or strings contain substrings.


describe('toContain Examples', () => {
  test('array containment', () => {
    expect([1, 2, 3, 4]).toContain(3);
    expect(['apple', 'banana', 'cherry']).toContain('banana');
    expect([{ id: 1 }, { id: 2 }]).toContain({ id: 1 }); // Deep equality
  });

  test('string containment', () => {
    expect('Hello World').toContain('World');
    expect('JavaScript').toContain('Script');
    expect('Hello World').not.toContain('Goodbye');
  });

  test('special values', () => {
    expect([1, null, 3]).toContain(null);
    expect([1, undefined, 3]).toContain(undefined);
    expect([1, NaN, 3]).toContain(NaN);
  });
});

toMatchObject(expected)

Tests partial object matching. The received object must contain all properties of the expected object.


describe('toMatchObject Examples', () => {
  test('partial matching', () => {
    const user = {
      id: 1,
      name: 'John',
      email: 'john@example.com',
      age: 30,
      preferences: { theme: 'dark' }
    };

    expect(user).toMatchObject({
      name: 'John',
      age: 30
    });

    expect(user).toMatchObject({
      preferences: { theme: 'dark' }
    });
  });

  test('nested partial matching', () => {
    const response = {
      data: {
        user: { id: 1, name: 'John' },
        meta: { total: 100, page: 1 }
      },
      status: 200
    };

    expect(response).toMatchObject({
      data: {
        user: { name: 'John' }
      },
      status: 200
    });
  });
});

toHaveProperty(path, value?)

Tests if objects have properties, optionally with specific values.


describe('toHaveProperty Examples', () => {
  test('simple properties', () => {
    const user = { name: 'John', age: 30 };

    expect(user).toHaveProperty('name');
    expect(user).toHaveProperty('name', 'John');
    expect(user).toHaveProperty('age', 30);
    expect(user).not.toHaveProperty('email');
  });

  test('nested properties', () => {
    const user = {
      name: 'John',
      address: {
        street: '123 Main St',
        city: 'New York',
        coordinates: { lat: 40.7, lng: -74.0 }
      }
    };

    expect(user).toHaveProperty('address.street');
    expect(user).toHaveProperty('address.street', '123 Main St');
    expect(user).toHaveProperty('address.coordinates.lat', 40.7);
  });

  test('array properties', () => {
    const data = {
      users: [
        { name: 'John' },
        { name: 'Jane' }
      ]
    };

    expect(data).toHaveProperty('users.0.name', 'John');
    expect(data).toHaveProperty('users.1.name', 'Jane');
  });
});

toMatchSchema(schema)

Advanced schema validation for complex data structures.


describe('Schema Matching', () => {
  test('object schema validation', () => {
    const userSchema = {
      name: 'string',
      age: 'number',
      email: 'string',
      active: 'boolean'
    };

    const user = {
      name: 'John Doe',
      age: 30,
      email: 'john@example.com',
      active: true
    };

    expect(user).toMatchSchema(userSchema);
  });

  test('nested schema validation', () => {
    const orderSchema = {
      id: 'string',
      user: {
        name: 'string',
        email: 'string'
      },
      items: 'array',
      total: 'number'
    };

    const order = {
      id: 'order-123',
      user: {
        name: 'John',
        email: 'john@example.com'
      },
      items: [{ name: 'Item 1' }],
      total: 99.99
    };

    expect(order).toMatchSchema(orderSchema);
  });
});

toHaveLength(count)

Tests the length of arrays, strings, and collection objects.


describe('toHaveLength Examples', () => {
  test('array length', () => {
    expect([1, 2, 3]).toHaveLength(3);
    expect([]).toHaveLength(0);
    expect(new Array(5)).toHaveLength(5);
  });

  test('string length', () => {
    expect('hello').toHaveLength(5);
    expect('').toHaveLength(0);
    expect('🚀').toHaveLength(2); // Emoji can be 2 chars
  });

  test('collection objects', () => {
    expect(new Set([1, 2, 3])).toHaveLength(3);
    expect(new Map([['a', 1], ['b', 2]])).toHaveLength(2);
    expect({ length: 5 }).toHaveLength(5); // Any object with length property
  });
});

toBeEmpty()


describe('toBeEmpty Examples', () => {
  test('empty checks', () => {
    expect([]).toBeEmpty();
    expect('').toBeEmpty();
    expect({}).toBeEmpty();
    expect(new Set()).toBeEmpty();
    expect(new Map()).toBeEmpty();
  });
});

toThrow(error?)

Tests if functions throw errors, optionally matching specific error types or messages.


describe('toThrow Examples', () => {
  test('basic throwing', () => {
    expect(() => {
      throw new Error('Something went wrong');
    }).toThrow();

    expect(() => {
      return 42; // Doesn't throw
    }).not.toThrow();
  });

  test('specific error messages', () => {
    expect(() => {
      throw new Error('Division by zero');
    }).toThrow('Division by zero');

    expect(() => {
      throw new Error('Invalid input provided');
    }).toThrow(/Invalid input/);
  });

  test('error types', () => {
    expect(() => {
      throw new TypeError('Not a function');
    }).toThrow(TypeError);

    expect(() => {
      throw new ReferenceError('Undefined variable');
    }).toThrow(ReferenceError);
  });

  test('async throwing', () => {
    // For async functions, see Async Testing documentation
    expect(Promise.reject(new Error('Async error'))).rejects.toThrow();
  });
});

toBeValidEmail()


describe('toBeValidEmail Examples', () => {
  test('email validation', () => {
    expect('user@example.com').toBeValidEmail();
    expect('test.user+tag@domain.co.uk').toBeValidEmail();
    expect('invalid-email').not.toBeValidEmail();
  });
});

toBeValidJSON()


describe('toBeValidJSON Examples', () => {
  test('JSON validation', () => {
    expect('{"name": "John"}').toBeValidJSON();
    expect('[1, 2, 3]').toBeValidJSON();
    expect('invalid json').not.toBeValidJSON();
  });
});

toBeValidURL()


describe('toBeValidURL Examples', () => {
  test('URL validation', () => {
    expect('https://example.com').toBeValidURL();
    expect('not-a-url').not.toBeValidURL();
  });
});

toBeValidUUID()


describe('toBeValidUUID Examples', () => {
  test('UUID validation', () => {
    expect('550e8400-e29b-41d4-a716-446655440000').toBeValidUUID();
    expect('not-a-uuid').not.toBeValidUUID();
  });
});

Async Matchers

For detailed information about async matchers: toResolve, toReject, see the Async Testing guide.

Mock Functions

For detailed information about mock functions: toHaveBeenCalled(), toHaveBeenCalledWith(), toHaveBeenCalledTimes(), toHaveBeenCalledWithExactly(), toHaveBeenLastCalledWith(), toHaveBeenNthCalledWith(), toHaveReturned(), toHaveReturnedWith(), toHaveLastReturnedWith(), toHaveNthReturnedWith(), toHaveThrown(), toHaveThrownWith(), toHaveLastReturnedWith(), toHaveNthReturnedWith(), toHaveThrown(), toHaveThrownWith(), see the Mock Functions guide.

Custom Matchers

Extend Fauji with your own domain-specific matchers for better test readability.


import { addMatchers } from 'fauji';

// Define custom matchers
addMatchers({
  toBeDivisibleBy(received, divisor) {
    return received % divisor === 0;
  },

  toBeValidCreditCard(received) {
    // Simplified Luhn algorithm check
    const cleaned = received.replace(/\s/g, '');
    return /^\d{13,19}$/.test(cleaned);
  },

  toHaveExactKeys(received, expectedKeys) {
    const actualKeys = Object.keys(received).sort();
    const expected = expectedKeys.sort();
    return actualKeys.length === expected.length &&
           actualKeys.every((key, index) => key === expected[index]);
  }
});

describe('Custom Matchers', () => {
  test('mathematical custom matcher', () => {
    expect(10).toBeDivisibleBy(2);
    expect(10).toBeDivisibleBy(5);
    expect(10).not.toBeDivisibleBy(3);
  });

  test('validation custom matcher', () => {
    expect('4532 1234 5678 9012').toBeValidCreditCard();
    expect('invalid-card').not.toBeValidCreditCard();
  });

  test('object structure matcher', () => {
    expect({ a: 1, b: 2, c: 3 }).toHaveExactKeys(['a', 'b', 'c']);
    expect({ a: 1, b: 2 }).not.toHaveExactKeys(['a', 'b', 'c']);
  });
});
Best Practice: Custom matchers extend expect globally for all your tests. Define them in a setup file that runs before your tests.