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
Sophisticated argument matching system with calledWith() extension for creating expectations based on specific argument patterns.
The calledWith method allows creating argument-specific expectations on mock functions, enabling precise behavior definition based on input parameters.
/**
* Creates argument-specific expectation for mock function calls
* @param args - Expected arguments that can be literal values or matchers
* @returns Jest mock instance for chaining additional expectations
*/
calledWith(...args: [...MatchersOrLiterals<Parameters<T>>]): jest.Mock<T>;Usage Examples:
import { mock, any, anyString, anyNumber } from "jest-mock-extended";
interface UserService {
updateUser: (id: string, data: UserData, options?: UpdateOptions) => Promise<User>;
sendNotification: (userId: string, message: string, priority: number) => void;
}
const userService = mock<UserService>();
// Literal argument matching
userService.updateUser
.calledWith("user123", { name: "John" }, { validate: true })
.mockResolvedValue({ id: "user123", name: "John" });
// Mixed literal and matcher arguments
userService.updateUser
.calledWith("admin", any(), { admin: true })
.mockResolvedValue({ id: "admin", name: "Administrator" });
// Matcher-only arguments
userService.sendNotification
.calledWith(anyString(), anyString(), anyNumber())
.mockReturnValue(undefined);
// Multiple expectations for same function
userService.updateUser
.calledWith(anyString(), { status: "inactive" }, any())
.mockRejectedValue(new Error("Cannot update inactive user"));Arguments in calledWith can be literal values or matcher objects that implement asymmetric matching patterns.
/**
* Type defining arguments that can be either literal values or matcher objects
*/
type MatchersOrLiterals<Y extends any[]> = {
[K in keyof Y]: MatcherLike<Y[K]> | Y[K]
};
/**
* Interface for matcher objects that implement Jest asymmetric matcher protocol
*/
interface MatcherLike<T> {
asymmetricMatch(other: unknown): boolean;
toString(): string;
getExpectedType?(): string;
toAsymmetricMatcher?(): string;
}Usage Examples:
import { mock, any, anyString, anyObject, isA, arrayIncludes } from "jest-mock-extended";
interface DataProcessor {
processItems: (items: Item[], config: ProcessConfig) => ProcessResult;
validateInput: (data: any, schema: ValidationSchema) => boolean;
}
const processor = mock<DataProcessor>();
// Complex matcher combinations
processor.processItems
.calledWith(
arrayIncludes({ type: "premium" }), // Array must include premium item
{
strict: true,
timeout: anyNumber(),
filters: anyObject()
}
)
.mockReturnValue({ success: true, processedCount: 5 });
// Class instance matching
class CustomError extends Error {
constructor(public code: number, message: string) {
super(message);
}
}
processor.validateInput
.calledWith(any(), isA(ValidationSchema))
.mockReturnValue(true);Creates a Jest mock function with calledWith extension functionality, providing low-level access to the argument matching system.
/**
* Creates a Jest mock function with calledWith extension
* @param param - Destructured configuration parameter with fallback implementation
* @returns Mock function with argument-specific expectation capabilities
*/
const calledWithFn: <T extends FunctionLike>({
fallbackMockImplementation,
}?: { fallbackMockImplementation?: T }) => CalledWithMock<T>;Usage Examples:
import { calledWithFn, anyString, any } from "jest-mock-extended";
type ApiFunction = (method: string, url: string, data?: any) => Promise<Response>;
// Create mock function with calledWith extension
const apiFunc = calledWithFn<ApiFunction>({
fallbackMockImplementation: () => Promise.reject(new Error("Unmocked call"))
});
// Set up argument-specific expectations
apiFunc.calledWith("GET", anyString(), undefined)
.mockResolvedValue(new Response("GET success"));
apiFunc.calledWith("POST", "/api/users", any())
.mockResolvedValue(new Response("POST success"));
// Test the mock function
expect(await apiFunc("GET", "/api/data")).resolves.toBeDefined();
expect(await apiFunc("POST", "/api/users", { name: "John" })).resolves.toBeDefined();
// Unmocked calls will use fallback implementation
expect(apiFunc("DELETE", "/api/users/1")).rejects.toThrow("Unmocked call");Standalone function mock creation with calledWith functionality for testing individual functions.
/**
* Creates a Jest function mock with calledWith extension
* @returns Typed mock function with argument matching capabilities
*/
function mockFn<T extends FunctionLike>(): CalledWithMock<T> & T;Usage Examples:
import { mockFn, anyString, any } from "jest-mock-extended";
// Type definition for the function
type ApiCall = (
method: "GET" | "POST" | "PUT" | "DELETE",
url: string,
data?: any
) => Promise<ApiResponse>;
// Create typed mock function
const apiCall = mockFn<ApiCall>();
// Set up method-specific behavior
apiCall
.calledWith("GET", anyString(), undefined)
.mockResolvedValue({ status: 200, data: {} });
apiCall
.calledWith("POST", "/users", any())
.mockResolvedValue({ status: 201, data: { id: "new-user" } });
apiCall
.calledWith("DELETE", anyString(), undefined)
.mockResolvedValue({ status: 204, data: null });
// Test the mock
expect(await apiCall("GET", "/users")).toEqual({ status: 200, data: {} });
expect(await apiCall("POST", "/users", { name: "John" })).toEqual({
status: 201,
data: { id: "new-user" }
});
// Verify call expectations
expect(apiCall).toHaveBeenCalledWith("GET", "/users", undefined);
expect(apiCall).toHaveBeenCalledWith("POST", "/users", { name: "John" });Complex argument matching patterns for sophisticated testing scenarios.
Usage Examples:
import { mock, captor, matches, anyString, any } from "jest-mock-extended";
interface EventEmitter {
emit: (event: string, data: any) => void;
on: (event: string, handler: (data: any) => void) => void;
}
const emitter = mock<EventEmitter>();
// Argument capturing
const eventCaptor = captor<string>();
const dataCaptor = captor<any>();
emitter.emit.calledWith(eventCaptor, dataCaptor).mockReturnValue(undefined);
emitter.emit("user:created", { id: "123", name: "John" });
// Access captured values
expect(eventCaptor.value).toBe("user:created");
expect(dataCaptor.value).toEqual({ id: "123", name: "John" });
// Custom matcher with complex logic
const isValidEmail = matches<string>((email) =>
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
);
interface EmailValidator {
validate: (email: string, domain?: string) => boolean;
}
const validator = mock<EmailValidator>();
validator.validate
.calledWith(isValidEmail, anyString())
.mockReturnValue(true);
validator.validate
.calledWith(matches((email) => !email.includes("@")), any())
.mockReturnValue(false);
// Test validation
expect(validator.validate("user@example.com", "example.com")).toBe(true);
expect(validator.validate("invalid-email", "example.com")).toBe(false);