Typescript Mock Imports
ts-mock-imports provides an intuitive way to mock ES6 `import` dependencies for TypeScript classes during unit testing, without requiring explicit dependency injection. It is built on top of `sinon` for stubbing capabilities and leverages TypeScript's module resolution to intercept imported classes. The library is currently at version 1.3.19 and primarily sees minor patch releases, focusing on dependency updates and bug fixes rather than rapid feature additions, indicating a mature and stable codebase. A key differentiator is its direct manipulation of imported modules to replace original classes with type-safe stub versions, enabling seamless testing of code that directly instantiates its dependencies. It intercepts and replaces actual class constructors or functions exported via ES6 `import` statements with Sinon stubs at runtime, allowing fine-grained control over dependencies without modifying the source code under test. It requires both `sinon` (version >= 4.1.2) and `typescript` (version >= 2.6.1) as peer dependencies to function correctly.
Common errors
-
TypeError: Cannot set property TestClass of #<Object> which has only a getter
cause Attempting to mock a class from a module that defines its exports with getters, preventing modification. This is common with certain TypeScript compilation targets or bundler configurations (e.g., Babel 7+, Webpack).fixReplace `ImportMock.mockClass` with `new InPlaceMockManager(module, 'ClassName');` to use an alternative mocking strategy that bypasses the getter restriction. -
ReferenceError: [ClassName] is not defined
cause The module path provided to `ImportMock.mockClass` in your test file does not precisely match the path used by the module under test that imports `ClassName`.fixVerify that `import * as myModule from './path/to/module';` in your test file uses the exact same relative or aliased path as the file that imports `ClassName` in your application code. -
Cannot find module 'sinon' or Cannot find module 'typescript'
cause The peer dependencies `sinon` and/or `typescript` are not installed in the project.fixInstall the required peer dependencies: `npm install sinon typescript --save-dev`. -
TypeError: module.exports is not a function or module.exports.default is not a constructor
cause `ts-mock-imports` is designed for mocking ES6 `import`s of classes. This error can occur if you're trying to mock a CommonJS `require`'d module, or if the export structure doesn't match an ES6 class export.fixEnsure the module you are trying to mock is indeed exporting a class via ES6 `export class MyClass { ... }` or `export default class MyClass { ... }` and is being consumed via `import` statements. The library is not compatible with `requirejs`.
Warnings
- gotcha When mocking a module, both the source file and the test file must use the *exact same path* to import the target module. Inconsistent import paths (e.g., `'./src/foo'` in production vs. `'src/index'` in tests for the same module) will prevent the mock from being applied correctly.
- breaking TypeScript versions 3.9 and later introduced changes that can cause a `TypeError: Cannot set property TestClass of #<Object> which has only a getter` when attempting to mock certain modules. This occurs because module exports are no longer enumerable, preventing `ts-mock-imports` from replacing the exported class.
- gotcha It is critical to call `ImportMock.restore()` after each test or test suite where mocks were applied. Failure to restore mocks can lead to global state pollution, causing tests to interfere with each other and produce flaky or incorrect results.
- gotcha While `ts-mock-imports` allows mocking ES6 `import`s, it relies on runtime manipulation of module exports. This mechanism is fundamentally a 'monkey patch' over Node's module system and may be less robust or compatible with native ESM environments where imports are often immutable.
- gotcha `ts-mock-imports` depends on `sinon` and `typescript` as peer dependencies. These must be installed manually in your project (typically as `devDependencies`) for the library to function correctly.
Install
-
npm install ts-mock-imports -
yarn add ts-mock-imports -
pnpm add ts-mock-imports
Imports
- ImportMock
const { ImportMock } = require('ts-mock-imports');import { ImportMock } from 'ts-mock-imports'; - MockManager
import { MockManager } from 'ts-mock-imports';import type { MockManager } from 'ts-mock-imports'; - InPlaceMockManager
const { InPlaceMockManager } = require('ts-mock-imports');import { InPlaceMockManager } from 'ts-mock-imports';
Quickstart
import { ImportMock } from 'ts-mock-imports';
import { Bar } from '../src/bar';
import * as fooModule from '../src/foo';
// Imagine 'Foo' class in '../src/foo.ts' has a constructor that throws an error
// and 'Bar' in '../src/bar.ts' instantiates 'Foo'.
describe('Bar', () => {
let mockFooManager: ImportMock.MockManager<any>;
beforeEach(() => {
// Mock the 'Foo' class from 'fooModule'
// This intercepts any 'new Foo()' call within modules that imported fooModule
mockFooManager = ImportMock.mockClass(fooModule, 'Foo');
});
afterEach(() => {
// Restore all mocks to their original implementations to ensure test isolation
ImportMock.restore();
});
it('should create an instance of Bar without error when Foo is mocked', () => {
// Now, new Bar() will use the mocked Foo, preventing the error
const bar = new Bar();
expect(bar).toBeInstanceOf(Bar);
});
it('should allow stubbing methods of the mocked Foo class', () => {
// Configure a mock response for the 'getCount' method of Foo
mockFooManager.mock('getCount', () => 42);
// If Bar were to call getCount internally, it would now return 42
// For demonstration, let's assume Bar had a method that called Foo.getCount()
// (This example focuses on the core mocking setup)
// const bar = new Bar();
// const result = bar.getFooCount(); // Assuming such a method exists
// expect(result).toBe(42);
const mockFooInstance = mockFooManager.get;// For a real test, you'd test Bar's interaction with Foo
});
});