CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-mm

Mock mate library for mocking functions, HTTP requests, and file system operations in Node.js testing

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

spy-functions.mddocs/

Spy Functionality

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.

Capabilities

Basic Spy Function

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]

Class Method Mocking

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)

Automatic Spy Properties

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" }]

Spy Validation

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"
}

Spy vs Mock Differences

Pure Spy (Original Behavior)

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); // => 1

Spy with Mock (Changed Behavior)

Using 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); // => 1

Advanced Spy Patterns

Conditional Spying

Spy 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); // => 0

Method Chain Spying

Spy 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); // => 1

Class Instance Tracking

Track 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); // => 1

Integration with Testing Frameworks

Spy 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");
  });
});

Types

// Spy properties added to all functions
interface SpyProperties {
  called: number;
  calledArguments: any[][];
  lastCalledArguments: any[];
}

// Property key types for spying
type PropertyKey = string | number | symbol;

docs

async-mocking.md

core-mocking.md

http-mocking.md

index.md

spy-functions.md

sync-mocking.md

system-mocking.md

tile.json