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']);
});
});
expect
globally for all your tests. Define them in a
setup file that runs before your tests.