Type safe mocking extensions for jest
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Extensive built-in matcher system with type-safe argument validation and support for custom matcher creation.
Foundation class for creating custom matchers that integrate with Jest's asymmetric matcher system.
/**
* Base class for creating custom matchers
*/
class Matcher<T> implements MatcherLike<T> {
$$typeof: symbol;
inverse?: boolean;
/**
* Creates a new matcher instance
* @param asymmetricMatch - Function that performs the actual matching logic
* @param description - Human-readable description of the matcher
*/
constructor(
readonly asymmetricMatch: MatcherFn<T>,
private readonly description: string
);
/** Returns string representation of the matcher */
toString(): string;
/** Returns asymmetric matcher representation */
toAsymmetricMatcher(): string;
/** Returns expected type for Jest error messages */
getExpectedType(): string;
}
/**
* Function type for matcher predicate functions
*/
type MatcherFn<T> = (actualValue: T) => boolean;Matchers for validating basic JavaScript types with full type safety.
/** Matches any value of any type */
const any: MatcherCreator<any>;
/** Matches any boolean value (true or false) */
const anyBoolean: MatcherCreator<boolean>;
/** Matches any string value including empty strings */
const anyString: MatcherCreator<string>;
/** Matches any number that is not NaN */
const anyNumber: MatcherCreator<number>;
/** Matches any function */
const anyFunction: MatcherCreator<Function>;
/** Matches any symbol */
const anySymbol: MatcherCreator<Symbol>;
/** Matches any non-null object */
const anyObject: MatcherCreator<any>;Usage Examples:
import { mock, any, anyString, anyNumber, anyBoolean } from "jest-mock-extended";
interface Calculator {
add: (a: number, b: number) => number;
concat: (str1: string, str2: string) => string;
compare: (val1: any, val2: any) => boolean;
}
const calc = mock<Calculator>();
// Type-specific matching
calc.add.calledWith(anyNumber(), anyNumber()).mockReturnValue(42);
calc.concat.calledWith(anyString(), anyString()).mockReturnValue("result");
calc.compare.calledWith(any(), any()).mockReturnValue(anyBoolean());
// Test calls
expect(calc.add(5, 10)).toBe(42);
expect(calc.concat("hello", "world")).toBe("result");
expect(typeof calc.compare("a", 1)).toBe("boolean");Matchers for validating arrays, maps, sets, and other collection types.
/** Matches any array */
const anyArray: MatcherCreator<any[]>;
/** Matches any Map instance */
const anyMap: MatcherCreator<Map<any, any>>;
/** Matches any Set instance */
const anySet: MatcherCreator<Set<any>>;
/**
* Matches instances of a specific class
* @param clazz - Constructor function to match against
*/
const isA: MatcherCreator<any>;Usage Examples:
import { mock, anyArray, anyMap, anySet, isA } from "jest-mock-extended";
class UserModel {
constructor(public id: string, public name: string) {}
}
interface DataService {
processArray: (items: any[]) => void;
processMap: (data: Map<string, any>) => void;
processSet: (unique: Set<string>) => void;
processUser: (user: UserModel) => void;
}
const service = mock<DataService>();
// Collection type matching
service.processArray.calledWith(anyArray()).mockReturnValue(undefined);
service.processMap.calledWith(anyMap()).mockReturnValue(undefined);
service.processSet.calledWith(anySet()).mockReturnValue(undefined);
// Class instance matching
service.processUser.calledWith(isA(UserModel)).mockReturnValue(undefined);
// Test with actual instances
service.processArray([1, 2, 3]);
service.processMap(new Map([["key", "value"]]));
service.processSet(new Set(["a", "b", "c"]));
service.processUser(new UserModel("123", "John"));Matchers for validating the contents of collections and objects.
/**
* Checks if array includes the specified value
* @param arrayVal - Value that should be present in the array
*/
const arrayIncludes: MatcherCreator<any[], any>;
/**
* Checks if Set contains the specified value
* @param setVal - Value that should be present in the Set
*/
const setHas: MatcherCreator<Set<any>, any>;
/**
* Checks if Map contains the specified key
* @param mapKey - Key that should be present in the Map
*/
const mapHas: MatcherCreator<Map<any, any>, any>;
/**
* Checks if object contains the specified key
* @param key - Property name that should exist on the object
*/
const objectContainsKey: MatcherCreator<any, string>;
/**
* Checks if object contains the specified value
* @param value - Value that should be present in object's values
*/
const objectContainsValue: MatcherCreator<any>;Usage Examples:
import {
mock,
arrayIncludes,
setHas,
mapHas,
objectContainsKey,
objectContainsValue
} from "jest-mock-extended";
interface SearchService {
searchInArray: (items: string[], query: string) => boolean;
searchInSet: (items: Set<string>, query: string) => boolean;
searchInMap: (data: Map<string, any>, key: string) => boolean;
searchInObject: (obj: any, criteria: any) => boolean;
}
const search = mock<SearchService>();
// Content validation
search.searchInArray
.calledWith(arrayIncludes("target"), "target")
.mockReturnValue(true);
search.searchInSet
.calledWith(setHas("item"), "item")
.mockReturnValue(true);
search.searchInMap
.calledWith(mapHas("key"), "key")
.mockReturnValue(true);
search.searchInObject
.calledWith(objectContainsKey("name"), "name")
.mockReturnValue(true);
search.searchInObject
.calledWith(objectContainsValue("John"), "John")
.mockReturnValue(true);
// Test with matching content
expect(search.searchInArray(["a", "target", "c"], "target")).toBe(true);
expect(search.searchInSet(new Set(["a", "item", "c"]), "item")).toBe(true);
expect(search.searchInMap(new Map([["key", "value"]]), "key")).toBe(true);
expect(search.searchInObject({ name: "John", age: 25 }, "name")).toBe(true);
expect(search.searchInObject({ name: "John", age: 25 }, "John")).toBe(true);Matchers for validating null, undefined, and empty values.
/** Matches values that are not null */
const notNull: MatcherCreator<any>;
/** Matches values that are not undefined */
const notUndefined: MatcherCreator<any>;
/** Matches values that are not null, undefined, or empty string */
const notEmpty: MatcherCreator<any>;Usage Examples:
import { mock, notNull, notUndefined, notEmpty } from "jest-mock-extended";
interface ValidationService {
validateRequired: (value: any) => boolean;
validateOptional: (value: any) => boolean;
validateNonEmpty: (value: any) => boolean;
}
const validator = mock<ValidationService>();
// Null/undefined validation
validator.validateRequired.calledWith(notNull()).mockReturnValue(true);
validator.validateOptional.calledWith(notUndefined()).mockReturnValue(true);
validator.validateNonEmpty.calledWith(notEmpty()).mockReturnValue(true);
// Test validation
expect(validator.validateRequired("value")).toBe(true);
expect(validator.validateOptional(0)).toBe(true);
expect(validator.validateNonEmpty("text")).toBe(true);
// These would not match the expectations
// validator.validateRequired(null); // Would not match notNull()
// validator.validateOptional(undefined); // Would not match notUndefined()
// validator.validateNonEmpty(""); // Would not match notEmpty()Special matcher for capturing argument values during mock function calls.
/**
* Captor matcher for capturing argument values
*/
class CaptorMatcher<T> {
$$typeof: symbol;
readonly asymmetricMatch: MatcherFn<T>;
/** Last captured value */
readonly value: T;
/** All captured values in order */
readonly values: T[];
constructor();
getExpectedType(): string;
toString(): string;
toAsymmetricMatcher(): string;
}
/**
* Creates an argument captor for capturing call arguments
* @returns Captor matcher instance
*/
const captor: <T = any>() => CaptorMatcher<T>;Usage Examples:
import { mock, captor } from "jest-mock-extended";
interface EventLogger {
logEvent: (event: string, data: any, timestamp: number) => void;
logError: (error: Error, context: string) => void;
}
const logger = mock<EventLogger>();
// Create captors for different argument types
const eventCaptor = captor<string>();
const dataCaptor = captor<any>();
const timestampCaptor = captor<number>();
// Set up captor expectations
logger.logEvent
.calledWith(eventCaptor, dataCaptor, timestampCaptor)
.mockReturnValue(undefined);
// Make calls that will be captured
logger.logEvent("user:login", { userId: "123" }, Date.now());
logger.logEvent("user:logout", { userId: "123", reason: "timeout" }, Date.now());
// Access captured values
expect(eventCaptor.value).toBe("user:logout"); // Last captured value
expect(eventCaptor.values).toEqual(["user:login", "user:logout"]); // All values
expect(dataCaptor.values[0]).toEqual({ userId: "123" });
expect(dataCaptor.values[1]).toEqual({ userId: "123", reason: "timeout" });
expect(typeof timestampCaptor.value).toBe("number");Create custom matchers with specific validation logic.
/**
* Creates a custom matcher from a predicate function
* @param matcher - Function that returns true for matching values
* @returns Custom matcher instance
*/
const matches: <T = any>(matcher: MatcherFn<T>) => Matcher<T>;
/**
* Interface for creating reusable matcher functions
*/
interface MatcherCreator<T, E = T> {
(expectedValue?: E): Matcher<T>;
}Usage Examples:
import { mock, matches, MatcherCreator, Matcher } from "jest-mock-extended";
interface StringValidator {
validateEmail: (email: string) => boolean;
validatePhone: (phone: string) => boolean;
validateLength: (text: string, min: number, max: number) => boolean;
}
const validator = mock<StringValidator>();
// Simple custom matcher
const isValidEmail = matches<string>((email) =>
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
);
validator.validateEmail.calledWith(isValidEmail).mockReturnValue(true);
// Reusable custom matcher creator
const hasLength: MatcherCreator<string, number> = (expectedLength) =>
new Matcher(
(actualValue) => actualValue.length === expectedLength,
`hasLength(${expectedLength})`
);
const isLongerThan: MatcherCreator<string, number> = (minLength) =>
new Matcher(
(actualValue) => actualValue.length > minLength,
`isLongerThan(${minLength})`
);
// Use custom matchers
validator.validateLength
.calledWith(hasLength(10), 5, 15)
.mockReturnValue(true);
validator.validatePhone
.calledWith(isLongerThan(9))
.mockReturnValue(true);
// Test with custom matchers
expect(validator.validateEmail("user@example.com")).toBe(true);
expect(validator.validateLength("1234567890", 5, 15)).toBe(true);
expect(validator.validatePhone("1234567890")).toBe(true);