Registry / testing / testdouble

testdouble

JSON →
library3.20.2jsnpmunverified

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.

npm install testdouble
INSTALL
IMPORT
SIG · TESTDOUBLE
T
testdouble
testingjavascriptv3.20.2
Install
—
Import
—
Disk
—
Pass rate
0/ 6
Env Coverage0 / 6
glibc
18–22
musl
18–22
Install & Compatibility
Where this runs
tested against v? · npm install
Install × environment matrix
Each cell = how many times install + import succeeded across repeated harness runs. Partial = flaky.
glibc = Debian/Ubuntu slim · musl = Alpine Linux
musl
node 18–226 runs
build_error
glibc
node 18–226 runs
build_error
Code
Verified usage

Verified import paths — ran on the pinned version, not inferred.

td
✓ import * as td from 'testdouble'
✗ const td = require('testdouble')
This is the recommended ES module import. For CommonJS, use `const td = require('testdouble')`.
td
✓ globalThis.td = require('testdouble')
✗ import * as td from 'testdouble'; globalThis.td = td;
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`.
TestDouble
✓ import type * as TestDouble from 'testdouble'
For TypeScript, this import provides type definitions without bundling the library itself.
td.replaceEsm
✓ import * as td from 'testdouble'; td.replaceEsm('./my-module', 'default', () => ({ ... }))
✗ import { replaceEsm } from 'testdouble'
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.

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.

import * as td from 'testdouble'; import assert from 'assert'; // Simulate a dependency module class EmailService { send(to: string, subject: string, body: string): boolean { console.log(`Sending email to ${to}: ${subject}`); return true; } } // Simulate a module under test that uses EmailService class UserService { constructor(private emailService: EmailService) {} registerUser(email: string, username: string): boolean { if (!this.emailService.send(email, 'Welcome!', `Hello ${username}, welcome!`)) { return false; } return true; } } // Test setup describe('UserService with testdouble', () => { let mockEmailService: EmailService; let userService: UserService; beforeEach(() => { // Replace the EmailService class with a test double mockEmailService = td.object<EmailService>(['send']); userService = new UserService(mockEmailService); }); afterEach(() => { td.reset(); // Clean up all test doubles after each test }); it('should register a user and send a welcome email', () => { // Stub the 'send' method to always return true td.when(mockEmailService.send('user@example.com', 'Welcome!', td.matchers.isA(String))) .thenReturn(true); const result = userService.registerUser('user@example.com', 'testuser'); assert.strictEqual(result, true); // Verify that the send method was called with expected arguments td.verify(mockEmailService.send('user@example.com', 'Welcome!', 'Hello testuser, welcome!'), { times: 1 }); }); it('should handle email sending failure', () => { // Stub the 'send' method to return false td.when(mockEmailService.send(td.matchers.anything(), td.matchers.anything(), td.matchers.anything())) .thenReturn(false); const result = userService.registerUser('fail@example.com', 'failuser'); assert.strictEqual(result, false); td.verify(mockEmailService.send(td.matchers.anything(), td.matchers.anything(), td.matchers.anything()), { times: 1 }); }); });
Debug
Known issues
breakingtestdouble.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.
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.
affects: >=3.0
gotchaThe 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.
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.
affects: >=1.0
gotchaUnderstanding 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.
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.
affects: >=3.0
gotchaLike 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.
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.
affects: >=1.0
Errors
Common errors & fixes
ReferenceError: td is not defined
Attempting to use the `td` global without properly importing or requiring `testdouble` and assigning it to `globalThis.td`.
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.
ESLint: 'td' is not defined. (no-undef)
Your linter (e.g., ESLint) does not recognize `td` as a global variable, particularly if you've opted for the global assignment pattern.
fix
Add `td` to your linter's global configuration. For ESLint, you might add `'td': true` under the `globals` section in your `.eslintrc` file.
Error: td.replaceEsm() requires two string arguments for the module and export name, not 'default'.
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.
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.
Upgrade
Version history
3.20.2latest on npm
Audit
Dependencies

No dependency data recorded yet.

Agent activity
8 hits · last 30 days
node
8
Resources