Mock mate library for mocking functions, HTTP requests, and file system operations in Node.js testing
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Track function calls without changing their behavior. Spy functions monitor call counts, arguments, and timing while preserving the original function's implementation and return values.
Create a spy wrapper around an existing function to track calls without modifying behavior.
/**
* Create a spy wrapper around existing function without changing behavior
* @param mod - Object containing the method to spy on
* @param method - Method name to spy on
* @throws Error if target is not a function
*/
function spy(mod: any, method: string | symbol): void;Usage Examples:
import { spy } from "mm";
const calculator = {
add: (a: number, b: number) => a + b,
multiply: async (a: number, b: number) => a * b
};
// Spy on synchronous function
spy(calculator, "add");
const result1 = calculator.add(2, 3); // => 5 (original behavior)
console.log(calculator.add.called); // => 1
console.log(calculator.add.lastCalledArguments); // => [2, 3]
// Spy on async function
spy(calculator, "multiply");
const result2 = await calculator.multiply(4, 5); // => 20 (original behavior)
console.log(calculator.multiply.called); // => 1
console.log(calculator.multiply.lastCalledArguments); // => [4, 5]Mock methods on class prototypes, affecting all instances of the class.
/**
* Mock method on class prototype affecting all instances
* @param instance - Any instance of the class to mock
* @param property - Method name to mock on the prototype
* @param value - Replacement method or value (optional)
*/
function classMethod(instance: any, property: PropertyKey, value?: any): void;Usage Examples:
import { classMethod } from "mm";
class UserService {
async fetchUser(id: string) {
// Real database call
return { id, name: "Real User", email: "real@example.com" };
}
}
const service1 = new UserService();
const service2 = new UserService();
// Mock affects all instances
classMethod(service1, "fetchUser", async (id: string) => ({
id,
name: "Mock User",
email: "mock@example.com"
}));
// Both instances now use the mock
const user1 = await service1.fetchUser("123"); // Mock data
const user2 = await service2.fetchUser("456"); // Mock data
console.log(service1.fetchUser.called); // => 1
console.log(service2.fetchUser.called); // => 1 (separate spy tracking)All mocked functions (including spies) automatically receive tracking properties:
interface SpyProperties {
/** Number of times the function has been called */
called: number;
/** Array containing arguments from each function call */
calledArguments: any[][];
/** Arguments from the most recent function call */
lastCalledArguments: any[];
}Usage Examples:
import { spy } from "mm";
const api = {
request: async (method: string, url: string, data?: any) => {
// Real API implementation
return { status: 200, data: "real response" };
}
};
spy(api, "request");
// Make several calls
await api.request("GET", "/users");
await api.request("POST", "/users", { name: "Alice" });
await api.request("PUT", "/users/1", { name: "Bob" });
// Check call tracking
console.log(api.request.called); // => 3
console.log(api.request.calledArguments);
// => [
// ["GET", "/users"],
// ["POST", "/users", { name: "Alice" }],
// ["PUT", "/users/1", { name: "Bob" }]
// ]
console.log(api.request.lastCalledArguments);
// => ["PUT", "/users/1", { name: "Bob" }]The spy function validates that the target is actually a function:
import { spy } from "mm";
const obj = {
value: 42,
calculate: (x: number) => x * 2
};
// This works - calculate is a function
spy(obj, "calculate");
// This throws an error - value is not a function
try {
spy(obj, "value");
} catch (err) {
console.log(err.message); // => "spy target value is not a function"
}Using spy() preserves the original function behavior:
import { spy } from "mm";
const math = {
square: (n: number) => n * n
};
spy(math, "square");
const result = math.square(5); // => 25 (real calculation)
console.log(math.square.called); // => 1Using mock() changes behavior but still adds spy properties:
import { mock } from "mm";
const math = {
square: (n: number) => n * n
};
mock(math, "square", (n: number) => 999); // Always return 999
const result = math.square(5); // => 999 (mocked behavior)
console.log(math.square.called); // => 1Spy on functions conditionally based on test requirements:
import { spy } from "mm";
const logger = {
debug: (msg: string) => console.log(`[DEBUG] ${msg}`),
info: (msg: string) => console.log(`[INFO] ${msg}`),
error: (msg: string) => console.error(`[ERROR] ${msg}`)
};
// Spy on all logger methods
Object.keys(logger).forEach(method => {
if (typeof logger[method] === "function") {
spy(logger, method);
}
});
logger.info("Test message");
logger.error("Test error");
console.log(logger.info.called); // => 1
console.log(logger.error.called); // => 1
console.log(logger.debug.called); // => 0Spy on method chains to track call sequences:
import { spy } from "mm";
const queryBuilder = {
select: function(fields: string) {
console.log(`SELECT ${fields}`);
return this;
},
where: function(condition: string) {
console.log(`WHERE ${condition}`);
return this;
},
execute: function() {
console.log("EXECUTE");
return "query results";
}
};
// Spy on all methods
spy(queryBuilder, "select");
spy(queryBuilder, "where");
spy(queryBuilder, "execute");
// Use the chain
const results = queryBuilder
.select("name, email")
.where("age > 18")
.execute();
console.log(queryBuilder.select.called); // => 1
console.log(queryBuilder.where.called); // => 1
console.log(queryBuilder.execute.called); // => 1Track method calls across different class instances:
import { spy } from "mm";
class DatabaseConnection {
connect() {
return "connected";
}
query(sql: string) {
return `results for: ${sql}`;
}
}
const db1 = new DatabaseConnection();
const db2 = new DatabaseConnection();
// Spy on instances separately
spy(db1, "query");
spy(db2, "query");
db1.query("SELECT * FROM users");
db2.query("SELECT * FROM posts");
db1.query("SELECT * FROM orders");
console.log(db1.query.called); // => 2
console.log(db2.query.called); // => 1Spy functionality integrates well with testing frameworks:
import { spy, restore } from "mm";
import { expect } from "chai";
describe("User Service", () => {
afterEach(() => {
restore(); // Clean up spies after each test
});
it("should call database twice for user details", async () => {
const db = {
query: async (sql: string) => [{ id: 1, name: "User" }]
};
const userService = new UserService(db);
spy(db, "query");
await userService.getUserWithProfile(1);
expect(db.query.called).to.equal(2);
expect(db.query.calledArguments[0][0]).to.include("SELECT * FROM users");
expect(db.query.calledArguments[1][0]).to.include("SELECT * FROM profiles");
});
});// Spy properties added to all functions
interface SpyProperties {
called: number;
calledArguments: any[][];
lastCalledArguments: any[];
}
// Property key types for spying
type PropertyKey = string | number | symbol;