Type safe mocking library for TypeScript
import { mock, when } from 'strong-mock';
interface Foo {
bar: (x: number) => string;
}
const foo = mock<Foo>();
when(() => foo.bar(23)).thenReturn('I am strong!');
console.log(foo.bar(23)); // 'I am strong!'
Table of Contents
undefined?Didn't expect mock to be called error?typeof mock() return function?undefined keys when setting expectations on objects?The mocks will share the same types as your production code, and you can safely refactor in an IDE knowing that all usages will be updated.

You can use matchers to partially match values, or create complex expectations, while still maintaining type safety.

Failed expectations will print a visual diff, and even integrate with the IDE.
import { mock, when } from 'strong-mock';
const fn = mock<(pos: { x: number; y: number }) => boolean>();
when(() =>
fn(
It.containsObject({
x: It.isNumber(),
y: It.matches<number>((y) => y > 0)
})
)
).thenReturn(true);
fn({ x: 1, y: -1 });

import { mock, when } from 'strong-mock';
const fn = mock<(id: number) => Promise<string>>();
when(() => fn(42)).thenResolve('foo');
when(() => fn(-1)).thenReject('oops');
console.log(await fn(42)); // foo
try {
await fn(-1);
} catch (e) {
console.log(e.message); // oops
}
strong-mock supports the latest LTS and Maintenance Node.js versions. It may work on older versions, but it's not guaranteed.
npm i -D strong-mock
yarn add -D strong-mock
pnpm add -D strong-mock
Pass in the type or interface to the generic argument of mock:
interface Foo {
bar: (x: number) => string;
baz: number;
}
const foo = mock<Foo>();
when(() => foo.bar(23)).thenReturn('awesome');
when(() => foo.baz).thenReturn(100);
console.log(foo.bar(23)); // 'awesome'
console.log(foo.baz); // 100
You can also mock function types:
type Fn = (x: number) => number;
const fn = mock<Fn>();
when(() => fn(1)).thenReturn(2);
console.log(fn(1)); // 2
Expectations are set by calling the mock inside a when callback and setting a return value.
when(() => foo.bar(23)).thenReturn('awesome');
You can set as many expectations as you want by calling when multiple times. If you have multiple expectations with the same arguments they will be consumed in the order they were created.
when(() => foo.bar(23)).thenReturn('awesome');
when(() => foo.bar(23)).thenReturn('even more awesome');
console.log(foo.bar(23)); // awesome
console.log(foo.bar(23)); // even more awesome
Sometimes you're not interested in specifying all the arguments in an expectation. Maybe they've been covered in another test, maybe they're hard to specify e.g. callbacks, or maybe you want to match just a property from an argument.
const fn = mock<
(x: number, data: { values: number[]; labels: string[] }) => string
>();
when(() => fn(
It.isNumber(),
It.containsObject({ values: [1, 2, 3] })
)).thenReturn('matched!');
console.log(fn(
123,
{ values: [1, 2, 3], labels: ['a', 'b', 'c'] })
); // 'matched!'
You can mix matchers with concrete arguments:
when(() => fn(42, It.isPlainObject())).thenReturn('matched');
Available matchers:
- deepEquals - the default (can be changed, uses deep equality,
- is - uses Object.is for comparison,
- isAny - matches anything,
- isNumber - matches any number,
- isString - matches any string, can search for substrings and patterns,
- isArray - matches any array, can search for subsets,
- isPlainObject - matches any plain object,
- containsObject - recursively matches a subset of an object,
- willCapture - matches anything and stores the received value,
- matches - build your own matcher.
The following table illustrates the differences between the equality matchers:
| expected | actual | It.is |
It.deepEquals |
It.deepEquals({ strict: false }) |
|---|---|---|---|---|
"foo" |
"foo" |
equal | equal | equal |
{ foo: "bar" } |
{ foo: "bar" } |
not equal | equal | equal |
{ } |
{ foo: undefined } |
not equal | not equal | equal |
new (class {})() |
new (class {})() |
not equal | not equal | equal |
You can nest matchers in deepEquals, containsObject and isArray:
type Point = { label: string; value: number };
const fn = mock<(data: { points: Point[]; title: string }) => number>();
// deepEquals is the default matcher so you can omit it.
when(() => fn({
points: It.isArray([
It.containsObject({
value: It.matches(x => x > 0)
})
]),
title: It.isString(/foo/)
})).thenReturn(100);
It.willCapture will match any value and store it, so you can access it outside an expectation. This could be useful to capture a callback and then test it separately.
type Cb = (value: number) => number;
const fn = mock<(cb: Cb) => number>();
const matcher = It.willCapture<Cb>();
when(() => fn(matcher)).thenReturn(42);
console.log(fn(23, (x) => x + 1)); // 42
console.log(matcher.value?.(3)); // 4
With It.matches you can create arbitrarily complex and type safe matchers:
const fn = mock<(x: number, y: string) => string>();
when(() => fn(
It.matches(x => x > 0),
It.matches(y => y.startsWith('foo'))
)).thenReturn('matched');
The types are automatically inferred, but you can also specify them explicitly through the generic parameter, which is useful if you want to create reusable matchers:
const startsWith = (expected: string) => It.matches<string>(
actual => actual.startsWith(expected)
);
when(() => fn(42, startsWith('foo'))).thenReturn('matched');
fn(42, 'foobar') // 'matched'
You can also customize how the matcher is printed in error messages, and how the diff is printed:
const closeTo = (expected: number, precision = 0.01) => It.matches<number>(
actual => Math.abs(expected - actual) <= precision,
{
toString: () => `closeTo(${expected}, ${precision})`,
getDiff: (actual) => {
const diff = Math.abs(expected - actual);
const sign = diff < 0 ? '-' : '+';
return {
actual: `${actual} (${sign}${diff})`,
expected: `${expected} ±${precision}`,
};
}
}
);
when(() => fn(closeTo(1), 'foo')).thenReturn('matched');
fn(2, 'foo');

By default, each call is expected to be made only once. You can expect a call to be made multiple times by using the invocation count helpers between, atLeast, times, anyTimes etc.:
const fn = mock<(x: number) => number>();
when(() => fn(1)).thenReturn(1).between(2, 3);
console.log(fn(1)); // 1
console.log(fn(1)); // 1
console.log(fn(1)); // 1
console.log(fn(1)); // throws because the expectation is finished
You'll notice there is no never() helper - if you expect a call to not be made simply don't set an expectation on it and the mock will throw if the call happens.
If you're mocking something that returns a promise then you'll be able to use the thenResolve promise helper to set the return value.
type Fn = (x: number) => Promise<number>;
const fn = mock<Fn>();
when(() => fn(1)).thenResolve(42);
console.log(await fn(1)); // 42
You can also use thenReturn with a Promise value:
when(() => fn(1)).thenReturn(Promise.resolve(42));
Use thenThrow or thenReject to throw an Error instance. You can customize the error message, or even pass a derived class.
type Fn = (x: number) => void;
type FnWithPromise = (x: number) => Promise<void>;
class MyError extends Error {}
const fn = mock<Fn>();
const fnWithPromise = mock<FnWithPromise>();
// All of these will throw an Error instance.
when(() => fn(1)).thenThrow();
when(() => fn(2)).thenThrow(MyError);
when(() => fnWithPromise(1)).thenReject('oops');
Calling verify(myMock) will make sure that all expectations set on the mock have been met, and that no additional calls have been made.
const fn = mock<(x: number) => number>();
when(() => fn(1)).thenReturn(1).between(2, 10);
verify(fn); // throws UnmetExpectations
It will also throw if any unexpected calls happened that were maybe caught in the code under test.
const fn = mock<() => void>();
try {
fn(); // throws because the call is unexpected
} catch(e) {
// your code might transition to an error state here
}
verify(fn); // throws UnexpectedCalls
It is recommended that you call verify() on your mocks at the end of every test. This will make sure you don't have any unused expectations in your tests and that your code did not silently catch any of the errors that are thrown when an unexpected call happens. You can use verifyAll() to check all existing mocks.
afterEach(() => {
verifyAll();
});

You can remove all expectations from a mock by using the reset() method:
const fn = mock<(x: number) => number>();
when(() => fn(1)).thenReturn(1);
reset(fn);
fn(1); // throws
If you create common mocks that are shared by multiple tests you should reset them before each test. You can use resetAll() to reset all existing mocks.
beforeEach(() => {
resetAll();
});
The following options can be set per mock, or globally with setDefaults.
import { mock, when, setDefaults } from 'strong-mock';
setDefaults({
exactParams: true
});
// Uses the new default.
const superStrictMock = mock<() => void>();
// Overrides the default.
const strictMock = mock<() => void>({ exactParams: false });
The name of a mock appears in error messages e.g., when an unexpected call happens. By default, all mocks are simply named mock, but you can set a custom name to make the error messages more descriptive.
```typescript import { mock, when } from 'strong-mock';
interface Service { / ... /} const service = mock({ name: 'ServiceMock' });
when(()
$ claude mcp add strong-mock \
-- python -m otcore.mcp_server <graph>