Simple and declarative mocks, spies, and replacements.
Combine with your unit test framework of choice.
npm install --save-dev test-quadruple
Other Package Managers
yarn add --dev test-quadruple
pnpm add --save-dev test-quadruple
import test from "ava";
import * as tq from "test-quadruple";
test("test-quadrule", async t => {
const logger = tq.spy();
const { bar, baz } = await tq.replace<typeof import("./foo.js")>({
modulePath: new URL("path/to/foo.js", import.meta.url),
importMeta: import.meta,
localMocks: {
"./bar.js": {
bar: tq.spy([
tq.returns("Hello, world!"),
tq.returns("lorem ipsum"),
]),
baz: tq.mock<Baz>({
bizz: "buzz",
}),
},
},
globalMocks: {
console: {
log: logger,
},
},
});
bar();
bar();
baz();
t.like(tq.explain(logger), {
callCount: 3,
called: true,
calls: [
{ arguments: ["Hello, world!"] },
{ arguments: ["lorem ipsum"] },
{ arguments: ["buzz"] },
],
flatCalls: [
"Hello, world!",
"lorem ipsum",
"buzz",
],
});
});
Word | Definition |
---|---|
Mock | A partial object that behaves as a full object on the type level. |
Spy | A wrapped (fake) function that tracks its calls. |
Fake | A function that behaves in a specific manner, used in place of another function. |
Replacement | A (partially) mocked/spied/faked module. |
A mock is a partial object that behaves as a full object on the type level.
Partially mocks an object.
Example
import * as tq from "test-quadruple";
type Person = {
name: string;
getAge: () => number;
parents?: {
mother?: Person;
father?: Person;
};
};
declare const storePerson: (person: Person) => void;
storePerson(tq.mock({
name: "John Doe",
parents: {
mother: {
name: "Jane Doe",
getAge: () => 42,
},
},
}));
Type: object
Default: {}
The partially-mocked object.
A spy is a wrapped function or set of fake functions that tracks its calls.
Wraps a function and tracks its calls.
Example
import * as tq from "test-quadruple";
const add = (a: number, b: number) => a + b;
const spy = tq.spy(add);
// ^? const spy: (a: number, b: number) => number
spy(1, 2);
//=> 3
spy(3, 4);
//=> 7
console.log(tq.explain(spy));
//=> { callCount: 2, called: true, calls: [{ arguments: [1, 2] }, { arguments: [3, 4] }] }
Type: function
The function to spy on.
Creates a spy that uses the provided fakes, in order. Subsequent calls will use the last provided fake.
Example
import * as tq from "test-quadruple";
const fn = tq.spy([tq.returns(1), tq.returns(2)]);
// OR: tq.spy(tq.returns(1), tq.returns(2));
fn();
//=> 1
fn();
//=> 2
fn();
//=> 2
Type: function[]
An optional array of fakes to use, in order.
List the tracked calls of a spy. Throws if the given function is not a spy.
Example
import test from "ava";
import * as tq from "test-quadruple";
test("tracks calls of a spy", async t => {
const fn = tq.spy(tq.resolves(1));
t.is(await fn(), 1);
t.like(tq.explain(fn), {
callCount: 1,
called: true,
calls: [{ arguments: [1, 2] }],
flatCalls: [1, 2],
});
});
Type: function
The spy to explain.
Type: object
An object with information about the spy.
Type: number
The number of times the given spy was called.
Type: boolean
Whether or not the given spy was called.
Type: Array<{ arguments: unknown[] }>
An array of calls to the given spy.
Type: unknown[]
A flattened array of calls to the given spy.
A fake is a function that behaves in a specific manner, used in place of another function. test-quadruple
provides a few utils to assist in faking behaviors.
Example
import * as tq from "test-quadruple";
const fn = tq.spy([
tq.returns(1),
tq.returns(2),
tq.throws(new Error("Oops! All errors!")),
tq.resolves(3),
tq.rejects("Oops! Not an error!"),
tq.noop(),
]);
fn();
//=> 1
fn();
//=> 2
fn();
//=> Error: "Oops! All errors!"
await fn();
//=> 3
await fn();
//=> "Oops! Not an error!"
fn();
//=> void
Creates a function that returns the given value
.
Creates a function that throws the given error
.
Creates an async function that resolves to the given value
.
Creates an async function that rejects with the given error
.
Creates a function that does nothing.
A replacement is a (partially) mocked, spied, or faked module. Replacements are done asynchronously via esmock
.
Partially replaces local or global imports of a module.
Example
import { execa } from "execa";
import { match, P } from "ts-pattern";
import * as tq from "test-quadruple";
const { program } = await tq.replace<typeof import('./program.js')>({
modulePath: new URL("program.js", import.meta.url),
importMeta: import.meta, // Required
localMocks: {
execa: {
execa: tq.spy(args => match(args)
.with("foo", tq.resolves("bar"))
.with([42, true], tq.resolves("The Ultimate Answer to Life, The Universe and Everything"))
.otherwise(execa)
),
},
},
});
await program("foo");
//=> "bar"
await program(42, true);
//=> "The Ultimate Answer to Life, The Universe and Everything"
await program("echo hi");
//=> "hi"
Type: object
Type: string | URL
The path to the module to replace.
Type: object
Pass in import.meta
.
Type: object
Mock modules directly imported by the source module.
Type: object
Mock modules or globals imported anywhere in the source tree.
- testtriple - A handy little mocking library.
- testdouble.js - A minimal test double library for TDD with JavaScript.
- tinyspy - A minimal fork of nanospy, with more features. Also tracks spy returns.
- @fluffy-spoon/substitute - A TypeScript port of NSubstitute.
- Sinon - Standalone and test framework agnostic JavaScript test spies, stubs and mocks.
- esmock - ESM import and globals mocking for unit tests.