A Test-Anything-Protocol library for JavaScript with comprehensive testing capabilities and plugin-based architecture
—
Module mocking, function interception, and call capturing for isolating tests and controlling dependencies.
Mock Node.js modules to control dependencies and isolate units under test.
/**
* Mock a CommonJS module with custom implementations
* @param module - Module name or path to mock
* @param mocks - Object containing mock implementations
*/
function mockRequire(module: string, mocks?: any): void;
/**
* Mock an ES module with custom implementations
* @param module - Module name or path to mock
* @param mocks - Object containing mock implementations
* @returns Promise that resolves when mock is registered
*/
function mockImport(module: string, mocks?: any): Promise<void>;
/**
* Create a mock object with base properties and overrides
* @param base - Base object to extend (optional)
* @param overrides - Properties to override or add
* @returns Mock object with specified properties
*/
function createMock(base?: any, overrides?: any): any;Usage Examples:
import { test, mockRequire, mockImport } from "tap";
// Mock CommonJS module
test("mock CommonJS fs module", (t) => {
mockRequire("fs", {
readFileSync: () => "mocked file content",
writeFileSync: () => undefined,
existsSync: () => true
});
const fs = require("fs");
const content = fs.readFileSync("any-file.txt");
t.equal(content, "mocked file content");
t.end();
});
// Mock ESM module
test("mock ES module", async (t) => {
await mockImport("node:fs/promises", {
readFile: async () => "mocked async content",
writeFile: async () => undefined
});
const { readFile } = await import("node:fs/promises");
const content = await readFile("any-file.txt");
t.equal(content, "mocked async content");
});
// Create mock objects
test("create mock objects", (t) => {
const mockUser = createMock({ id: 1 }, {
name: "Test User",
save: () => Promise.resolve()
});
t.equal(mockUser.id, 1);
t.equal(mockUser.name, "Test User");
t.type(mockUser.save, "function");
t.end();
});Intercept and monitor function calls for testing behavior and side effects.
/**
* Intercept property access on an object
* @param object - Target object to intercept
* @param property - Property name to intercept
* @param options - Interception configuration options
*/
function intercept(object: any, property: string, options?: InterceptOpts): void;
/**
* Capture function calls with detailed information
* @param fn - Function to capture calls for
* @returns Captured function with call information
*/
function captureFn(fn: Function): CapturedFunction;
/**
* Capture method calls on an object
* @param object - Target object
* @param property - Method name to capture
* @param fn - Optional replacement function
* @returns Original method for restoration
*/
function capture(object: any, property: string, fn?: Function): Function;Usage Examples:
// Intercept console.log calls
test("intercept console output", (t) => {
const messages: string[] = [];
intercept(console, "log", {
value: (message: string) => {
messages.push(message);
}
});
console.log("Hello");
console.log("World");
t.same(messages, ["Hello", "World"]);
t.end();
});
// Capture function calls
test("capture function calls", (t) => {
const originalFn = (x: number, y: number) => x + y;
const captured = captureFn(originalFn);
const result1 = captured(2, 3);
const result2 = captured(5, 7);
t.equal(result1, 5);
t.equal(result2, 12);
t.equal(captured.calls.length, 2);
t.same(captured.calls[0].args, [2, 3]);
t.same(captured.calls[1].args, [5, 7]);
t.end();
});
// Capture method calls
test("capture method calls", (t) => {
const api = {
save: (data: any) => Promise.resolve({ id: 1, ...data }),
delete: (id: number) => Promise.resolve()
};
const originalSave = capture(api, "save");
api.save({ name: "test" });
api.save({ name: "test2" });
// Access captured calls
t.equal(api.save.calls.length, 2);
t.same(api.save.calls[0].args, [{ name: "test" }]);
// Restore original
api.save = originalSave;
t.end();
});More sophisticated mocking scenarios for complex testing needs.
// Mock with dynamic behavior
test("dynamic mock behavior", (t) => {
let callCount = 0;
mockRequire("axios", {
get: (url: string) => {
callCount++;
if (callCount === 1) {
return Promise.resolve({ data: "first call" });
} else {
return Promise.reject(new Error("rate limited"));
}
}
});
const axios = require("axios");
axios.get("http://api.example.com")
.then((response: any) => {
t.equal(response.data, "first call");
return axios.get("http://api.example.com");
})
.catch((error: Error) => {
t.match(error.message, /rate limited/);
t.end();
});
});
// Mock with state tracking
test("stateful mocks", (t) => {
const mockDatabase = {
data: new Map(),
save: function(id: string, value: any) {
this.data.set(id, value);
return Promise.resolve(value);
},
find: function(id: string) {
return Promise.resolve(this.data.get(id));
},
clear: function() {
this.data.clear();
}
};
mockRequire("./database", mockDatabase);
const db = require("./database");
db.save("user1", { name: "Alice" })
.then(() => db.find("user1"))
.then((user: any) => {
t.same(user, { name: "Alice" });
t.end();
});
});Mock only specific parts of modules while preserving other functionality.
test("partial module mocking", async (t) => {
// Only mock specific methods of fs module
const originalFs = await import("node:fs/promises");
await mockImport("node:fs/promises", {
...originalFs,
readFile: async () => "mocked content",
// Other methods remain unchanged
});
const fs = await import("node:fs/promises");
const content = await fs.readFile("test.txt");
t.equal(content, "mocked content");
// fs.writeFile, fs.stat, etc. still work normally
});Properly clean up mocks to avoid test interference.
test("mock cleanup", (t) => {
// Store original for restoration
const originalLog = console.log;
let logMessages: string[] = [];
intercept(console, "log", {
value: (message: string) => {
logMessages.push(message);
}
});
console.log("test message");
t.same(logMessages, ["test message"]);
// Restore original (TAP usually handles this automatically)
console.log = originalLog;
t.end();
});interface InterceptOpts {
/** Replacement value or function */
value?: any;
/** Whether to call original after intercept */
callOriginal?: boolean;
/** Function to call before original */
before?: Function;
/** Function to call after original */
after?: Function;
}
interface CapturedFunction extends Function {
/** Array of captured call information */
calls: CallInfo[];
/** Original function that was captured */
original: Function;
/** Reset captured calls */
reset(): void;
}
interface CallInfo {
/** Arguments passed to the function */
args: any[];
/** Return value of the function call */
result?: any;
/** Error thrown by the function (if any) */
error?: Error;
/** Timestamp of the call */
timestamp: number;
/** Context (this value) of the call */
context?: any;
}Common patterns for mocking external services and dependencies.
// Mock HTTP client
test("HTTP service testing", async (t) => {
await mockImport("node:http", {
request: (options: any, callback: Function) => {
const mockResponse = {
statusCode: 200,
headers: { "content-type": "application/json" },
on: (event: string, handler: Function) => {
if (event === "data") {
handler('{"success": true}');
}
if (event === "end") {
handler();
}
}
};
callback(mockResponse);
return { end: () => {}, on: () => {} };
}
});
// Your HTTP service code here
// const result = await httpService.get("http://api.example.com");
// t.ok(result.success);
});
// Mock environment variables
test("environment-dependent code", (t) => {
const originalEnv = process.env.NODE_ENV;
process.env.NODE_ENV = "test";
// Test environment-specific behavior
t.equal(getEnvironment(), "test");
// Restore
process.env.NODE_ENV = originalEnv;
t.end();
});
// Mock timers
test("time-dependent code", (t) => {
const originalSetTimeout = global.setTimeout;
let timeoutCallback: Function;
global.setTimeout = (callback: Function, delay: number) => {
timeoutCallback = callback;
return 1; // Mock timer ID
};
startTimedProcess();
// Manually trigger timeout
timeoutCallback();
t.ok(isProcessComplete());
// Restore
global.setTimeout = originalSetTimeout;
t.end();
});Install with Tessl CLI
npx tessl i tessl/npm-tap