{"id":18070,"library":"testdouble","title":"testdouble.js","description":"testdouble.js (AKA td.js) is an opinionated, minimalist test double library for JavaScript and TypeScript, designed to facilitate test-driven development (TDD). It provides robust mechanisms for creating mocks, stubs, and spies to replace real dependencies within tests, promoting terse, clear, and easy-to-understand test suites. As of version 3.20.2, the library is actively maintained by Test Double, a software agency. It maintains a steady release cadence for bug fixes and minor features within its major versions. The library is framework-agnostic, compatible with popular test runners like Jest, Mocha, and Jasmine, and functions reliably in both Node.js and browser environments. Its primary differentiator is its strong opinions on TDD practices, aiming to guide developers toward healthier mocking patterns rather than simply offering a comprehensive feature set without guidance.","status":"active","version":"3.20.2","language":"javascript","source_language":"en","source_url":"https://github.com/testdouble/testdouble.js","tags":["javascript","tdd","bdd","mock","stub","spy","test double","double","typescript"],"install":[{"cmd":"npm install testdouble","lang":"bash","label":"npm"},{"cmd":"yarn add testdouble","lang":"bash","label":"yarn"},{"cmd":"pnpm add testdouble","lang":"bash","label":"pnpm"}],"dependencies":[],"imports":[{"note":"This is the recommended ES module import. For CommonJS, use `const td = require('testdouble')`.","wrong":"const td = require('testdouble')","symbol":"td","correct":"import * as td from 'testdouble'"},{"note":"While discouraged in favor of direct imports, `testdouble.js` suggests setting `td` globally for convenience, particularly in CommonJS setups. Linters may require configuration to ignore the global `td`.","wrong":"import * as td from 'testdouble'; globalThis.td = td;","symbol":"td","correct":"globalThis.td = require('testdouble')"},{"note":"For TypeScript, this import provides type definitions without bundling the library itself.","symbol":"TestDouble","correct":"import type * as TestDouble from 'testdouble'"},{"note":"Specific functions like `replaceEsm` are accessed via the `td` namespace. `td.replaceEsm` is crucial for mocking ES modules, differentiating it from `td.replace` for CommonJS or default exports.","wrong":"import { replaceEsm } from 'testdouble'","symbol":"td.replaceEsm","correct":"import * as td from 'testdouble'; td.replaceEsm('./my-module', 'default', () => ({ ... }))"}],"quickstart":{"code":"import * as td from 'testdouble';\nimport assert from 'assert';\n\n// Simulate a dependency module\nclass EmailService {\n  send(to: string, subject: string, body: string): boolean {\n    console.log(`Sending email to ${to}: ${subject}`);\n    return true;\n  }\n}\n\n// Simulate a module under test that uses EmailService\nclass UserService {\n  constructor(private emailService: EmailService) {}\n\n  registerUser(email: string, username: string): boolean {\n    if (!this.emailService.send(email, 'Welcome!', `Hello ${username}, welcome!`)) {\n      return false;\n    }\n    return true;\n  }\n}\n\n// Test setup\ndescribe('UserService with testdouble', () => {\n  let mockEmailService: EmailService;\n  let userService: UserService;\n\n  beforeEach(() => {\n    // Replace the EmailService class with a test double\n    mockEmailService = td.object<EmailService>(['send']);\n    userService = new UserService(mockEmailService);\n  });\n\n  afterEach(() => {\n    td.reset(); // Clean up all test doubles after each test\n  });\n\n  it('should register a user and send a welcome email', () => {\n    // Stub the 'send' method to always return true\n    td.when(mockEmailService.send('user@example.com', 'Welcome!', td.matchers.isA(String)))\n      .thenReturn(true);\n\n    const result = userService.registerUser('user@example.com', 'testuser');\n\n    assert.strictEqual(result, true);\n    // Verify that the send method was called with expected arguments\n    td.verify(mockEmailService.send('user@example.com', 'Welcome!', 'Hello testuser, welcome!'), { times: 1 });\n  });\n\n  it('should handle email sending failure', () => {\n    // Stub the 'send' method to return false\n    td.when(mockEmailService.send(td.matchers.anything(), td.matchers.anything(), td.matchers.anything()))\n      .thenReturn(false);\n\n    const result = userService.registerUser('fail@example.com', 'failuser');\n\n    assert.strictEqual(result, false);\n    td.verify(mockEmailService.send(td.matchers.anything(), td.matchers.anything(), td.matchers.anything()), { times: 1 });\n  });\n});","lang":"typescript","description":"This quickstart demonstrates how to create a test double for a class dependency using `td.object`, stub its methods with `td.when().thenReturn()`, and verify interactions with `td.verify()` within a `mocha`-like test structure. It also shows `td.reset()` for cleanup and basic argument matching."},"warnings":[{"fix":"Review the official documentation for `td.replace()` and `td.replaceEsm()`. Ensure `td.replaceEsm()` is used for ES Modules, requiring specific build tool configurations (e.g., Vite, Webpack, Node's `--loader`) to work correctly.","message":"testdouble.js v3 introduced significant changes to how module replacement works, particularly with `td.replaceEsm()` to correctly handle ES Modules. Previous versions might have relied on less robust or different patterns for module replacement which are no longer supported or behave differently.","severity":"breaking","affected_versions":">=3.0"},{"fix":"Configure your linter (e.g., ESLint, StandardJS) to acknowledge `td` as a global variable. Refer to your linter's documentation for adding global exclusions.","message":"The library recommends optionally setting `td` globally (e.g., `globalThis.td = require('testdouble')`). This can lead to linting errors (e.g., 'td is not defined') if your linter is not configured to recognize `td` as a global variable.","severity":"gotcha","affected_versions":">=1.0"},{"fix":"Always use `td.replaceEsm()` when mocking ES Modules. Be aware that `td.replaceEsm()` might require specific test environment setups (e.g., Node.js with `--loader` flags or bundler configurations like Vite/Webpack) to intercept module imports effectively. Consult the `testdouble.js` documentation for detailed guidance on your module system.","message":"Understanding the distinction between `td.replace()` and `td.replaceEsm()` is critical for correct module mocking. `td.replaceEsm()` is specifically designed for ES Modules and has different usage requirements and implications compared to `td.replace()` which primarily targets CommonJS or default exports.","severity":"gotcha","affected_versions":">=3.0"},{"fix":"Focus on testing behavior rather than implementation details. Use test doubles only for external dependencies that cannot be controlled in a test. Avoid mocking value objects or closely coupled internal components. Refer to the 'xUnit Test Patterns' book and `testdouble.js`'s extensive documentation on healthy TDD practices.","message":"Like many mocking libraries, `testdouble.js` can be abused, leading to over-mocked, brittle tests that provide little confidence. The library's maintainers themselves caution against common anti-patterns.","severity":"gotcha","affected_versions":">=1.0"}],"env_vars":null,"last_verified":"2026-04-25T00:00:00.000Z","next_check":"2026-07-24T00:00:00.000Z","problems":[{"fix":"Ensure `import * as td from 'testdouble'` (ESM) or `globalThis.td = require('testdouble')` (CommonJS) is executed before any `td` calls in your test files or setup scripts.","cause":"Attempting to use the `td` global without properly importing or requiring `testdouble` and assigning it to `globalThis.td`.","error":"ReferenceError: td is not defined"},{"fix":"Add `td` to your linter's global configuration. For ESLint, you might add `'td': true` under the `globals` section in your `.eslintrc` file.","cause":"Your linter (e.g., ESLint) does not recognize `td` as a global variable, particularly if you've opted for the global assignment pattern.","error":"ESLint: 'td' is not defined. (no-undef)"},{"fix":"When replacing the default export of an ESM, you typically provide the module path and then the *keyword* 'default' as the second argument, e.g., `td.replaceEsm('./my-module', 'default', () => ({...}))`. If targeting a named export, provide its specific name.","cause":"Incorrect usage of `td.replaceEsm()`, passing 'default' as the second argument when it expects a specific export name or a path for the default export.","error":"Error: td.replaceEsm() requires two string arguments for the module and export name, not 'default'."}],"ecosystem":"npm","meta_description":null,"install_score":null,"install_tag":null,"quickstart_score":null,"quickstart_tag":null}