CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-tap

A Test-Anything-Protocol library for JavaScript with comprehensive testing capabilities and plugin-based architecture

Pending
Overview
Eval results
Files

mocking.mddocs/

Mocking and Interception

Module mocking, function interception, and call capturing for isolating tests and controlling dependencies.

Capabilities

Module Mocking

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();
});

Function Interception

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();
});

Advanced Mocking Patterns

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();
    });
});

Partial Mocking

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

Mock Cleanup and Restoration

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();
});

Types

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

Testing with External Dependencies

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

docs

assertions.md

index.md

lifecycle-hooks.md

mocking.md

subprocess-testing.md

test-organization.md

tile.json