Spy4js Testing Spy Framework
Spy4js is a standalone JavaScript and TypeScript testing spy framework designed for integration with test runners like Vitest and Jest. It provides a robust API for creating and managing spies, focusing on test readability, detailed error messages, and efficient serialization of call arguments. The package aims to offer an intuitive alternative or supplement to the built-in spying capabilities of popular test frameworks. The current stable version is 5.0.0, released in September 2025. While release cadence can be irregular, significant updates (like the TypeScript migration in v3.0.0) introduce notable breaking changes. Key differentiators include an API optimized for readability, enhanced error reporting with detailed comparisons, and features like customizable behavior and module mocking capabilities for both CommonJS and ES Modules.
Common errors
-
TypeError: Spy is not a constructor
cause Attempting to create a spy instance using `new Spy()` after upgrading to spy4js v3.0.0 or higher.fixRemove the `new` keyword. Instantiate the spy by calling `Spy()` directly, e.g., `const mySpy = Spy();`. -
Error: spy.hasCallHistory() must be called with arguments or use wasNotCalled()
cause Invoking `spy.hasCallHistory()` without any arguments, which is deprecated and causes an error since spy4js v3.1.0.fixIf you intend to check if the spy was never called, use `spy.wasNotCalled()`. If you want to check for specific call patterns, provide arguments to `spy.hasCallHistory(...)`. -
ERR_REQUIRE_ESM (or similar module resolution error)
cause Attempting to use `require()` for module mocking with `Spy.mock` in an ES Module context, or vice-versa.fixEnsure that your module mocking strategy aligns with your project's module system. For ES Modules (e.g., in Vitest), use `await import()` with `vi.mock()`. For CommonJS (e.g., in Jest without ESM enabled), use `require()`.
Warnings
- breaking Starting from v3.0.0, the main `Spy` export is no longer a class and cannot be instantiated with the `new` keyword. It is now a callable object.
- breaking As of v3.1.0, calling `spy.hasCallHistory()` without any arguments will always fail. This method now requires arguments to check for specific call history.
- gotcha The `spy4js` library explicitly states that it is not strictly necessary as most modern test frameworks (like Jest and Vitest) already include their own spying capabilities.
- gotcha Version 3.1.0 introduced an 'enforce-order mode' which can be enabled via `Spy.configure({ enforceOrder: true })`. Enabling this significantly changes how spy calls are matched and validated, requiring calls to happen in a specific sequence.
Install
-
npm install spy4js -
yarn add spy4js -
pnpm add spy4js
Imports
- Spy
const Spy = require('spy4js');import { Spy } from 'spy4js'; - Spy.on
const spy = new Spy.on(someObject, 'methodName');
import { Spy } from 'spy4js'; const spy = Spy.on(someObject, 'methodName'); - Spy.mock
const moduleMocks = Spy.mock(require('./my-module'), 'useMe');import { Spy } from 'spy4js'; const moduleMocks = Spy.mock(await import('./my-module'), 'useMe');
Quickstart
import { Spy } from 'spy4js';
// Initialize a basic spy
const mySpy = Spy('myFunctionSpy');
// Simulate calling the spy
mySpy(1, 2, 'hello');
mySpy({ data: 'test' });
// Assertions
if (!mySpy.wasCalled()) {
throw new Error('mySpy was not called!');
}
if (!mySpy.wasCalledWith(1, 2, 'hello')) {
throw new Error('mySpy was not called with expected arguments!');
}
if (mySpy.getCallCount() !== 2) {
throw new Error(`Expected 2 calls, got ${mySpy.getCallCount()}`);
}
console.log('Spy calls:', mySpy.getAllCallArguments());
// Mock an existing object's method
const service = {
getData: (id: string) => `Data for ${id}`,
process: () => 'done'
};
const getDataSpy = Spy.on(service, 'getData').returns('Mocked Data');
console.log(service.getData('123')); // Outputs: Mocked Data
if (!getDataSpy.wasCalledWith('123')) {
throw new Error('getDataSpy was not called as expected!');
}
console.log('All good with spy4js!');