A mocking and spying library.
Test spies are function stand-ins that are used to assert if a function's internal behavior matches expectations. Test spies on methods keep the original behavior but allow you to test how the method is called and what it returns. Test stubs are an extension of test spies that also replaces the original methods behavior.
Spying
Say we have two functions, square and multiply, if we want to assert that
the multiply function is called during execution of the square function we
need a way to spy on the multiply function. There are a few ways to achieve
this with Spies, one is to have the square function take the multiply
multiply as a parameter.
This way, we can call square(multiply, value) in the application code or wrap
a spy function around the multiply function and call
square(multiplySpy, value) in the testing code.
import { assertSpyCall, assertSpyCalls, spy, } from "@std/testing/mock"; import { assertEquals } from "@std/assert/assert_equals"; function multiply(a: number, b: number): number { return a * b; } function square( multiplyFn: (a: number, b: number) => number, value: number, ): number { return multiplyFn(value, value); } Deno.test("square calls multiply and returns results", () => { const multiplySpy = spy(multiply); assertEquals(square(multiplySpy, 5), 25); // asserts that multiplySpy was called at least once and details about the first call. assertSpyCall(multiplySpy, 0, { args: [5, 5], returned: 25, }); // asserts that multiplySpy was only called once. assertSpyCalls(multiplySpy, 1); });
If you prefer not adding additional parameters for testing purposes only, you
can use spy to wrap a method on an object instead. In the following example, the
exported _internals object has the multiply function we want to call as a
method and the square function calls _internals.multiply instead of
multiply.
This way, we can call square(value) in both the application code and testing
code. Then spy on the multiply method on the _internals object in the
testing code to be able to spy on how the square function calls the multiply
function.
import { assertSpyCall, assertSpyCalls, spy, } from "@std/testing/mock"; import { assertEquals } from "@std/assert/assert_equals"; function multiply(a: number, b: number): number { return a * b; } function square(value: number): number { return _internals.multiply(value, value); } const _internals = { multiply }; Deno.test("square calls multiply and returns results", () => { const multiplySpy = spy(_internals, "multiply"); try { assertEquals(square(5), 25); } finally { // unwraps the multiply method on the _internals object multiplySpy.restore(); } // asserts that multiplySpy was called at least once and details about the first call. assertSpyCall(multiplySpy, 0, { args: [5, 5], returned: 25, }); // asserts that multiplySpy was only called once. assertSpyCalls(multiplySpy, 1); });
One difference you may have noticed between these two examples is that in the
second we call the restore method on multiplySpy function. That is needed to
remove the spy wrapper from the _internals object's multiply method. The
restore method is called in a finally block to ensure that it is restored
whether or not the assertion in the try block is successful. The restore
method didn't need to be called in the first example because the multiply
function was not modified in any way like the _internals object was in the
second example.
Stubbing
Say we have two functions, randomMultiple and randomInt, if we want to
assert that randomInt is called during execution of randomMultiple we need a
way to spy on the randomInt function. That could be done with either of the
spying techniques previously mentioned. To be able to verify that the
randomMultiple function returns the value we expect it to for what randomInt
returns, the easiest way would be to replace the randomInt function's behavior
with more predictable behavior.
You could use the first spying technique to do that but that would require
adding a randomInt parameter to the randomMultiple function.
You could also use the second spying technique to do that, but your assertions
would not be as predictable due to the randomInt function returning random
values.
Say we want to verify it returns correct values for both negative and positive
random integers. We could easily do that with stubbing. The below example is
similar to the second spying technique example but instead of passing the call
through to the original randomInt function, we are going to replace
randomInt with a function that returns pre-defined values.
The mock module includes some helper functions to make creating common stubs
easy. The returnsNext function takes an array of values we want it to return
on consecutive calls.
import { assertSpyCall, assertSpyCalls, returnsNext, stub, } from "@std/testing/mock"; import { assertEquals } from "@std/assert/assert_equals"; function randomInt(lowerBound: number, upperBound: number): number { return lowerBound + Math.floor(Math.random() * (upperBound - lowerBound)); } function randomMultiple(value: number): number { return value * _internals.randomInt(-10, 10); } const _internals = { randomInt }; Deno.test("randomMultiple uses randomInt to generate random multiples between -10 and 10 times the value", () => { const randomIntStub = stub(_internals, "randomInt", returnsNext([-3, 3])); try { assertEquals(randomMultiple(5), -15); assertEquals(randomMultiple(5), 15); } finally { // unwraps the randomInt method on the _internals object randomIntStub.restore(); } // asserts that randomIntStub was called at least once and details about the first call. assertSpyCall(randomIntStub, 0, { args: [-10, 10], returned: -3, }); // asserts that randomIntStub was called at least twice and details about the second call. assertSpyCall(randomIntStub, 1, { args: [-10, 10], returned: 3, }); // asserts that randomIntStub was only called twice. assertSpyCalls(randomIntStub, 2); });
Faking time
Say we have a function that has time based behavior that we would like to test. With real time, that could cause tests to take much longer than they should. If you fake time, you could simulate how your function would behave over time starting from any point in time. Below is an example where we want to test that the callback is called every second.
With FakeTime we can do that. When the FakeTime instance is created, it
splits from real time. The Date, setTimeout, clearTimeout, setInterval
and clearInterval globals are replaced with versions that use the fake time
until real time is restored. You can control how time ticks forward with the
tick method on the FakeTime instance.
import { assertSpyCalls, spy, } from "@std/testing/mock"; import { FakeTime } from "@std/testing/time"; function secondInterval(cb: () => void): number { return setInterval(cb, 1000); } Deno.test("secondInterval calls callback every second and stops after being cleared", () => { using time = new FakeTime(); const cb = spy(); const intervalId = secondInterval(cb); assertSpyCalls(cb, 0); time.tick(500); assertSpyCalls(cb, 0); time.tick(500); assertSpyCalls(cb, 1); time.tick(3500); assertSpyCalls(cb, 4); clearInterval(intervalId); time.tick(1000); assertSpyCalls(cb, 4); });
This module is browser compatible.
Classes
An error related to spying on a function or instance method.
Functions
Asserts that a spy is called as expected.
Asserts that a spy is called with a specific arg as expected.
Asserts that an spy is called with a specific range of args as expected. If a start and end index is not provided, the expected will be compared against all args. If a start is provided without an end index, the expected will be compared against all args from the start index to the end. The end index is not included in the range of args that are compared.
Asserts that an async spy is called as expected.
Asserts that a spy is called as much as expected and no more.
Creates a session that tracks all mocks created before it's restored. If a callback is provided, it restores all mocks created within it.
Creates an async session that tracks all mocks created before the promise resolves.
| AsyncIterable<Return | Error | Promise<Return | Error>>
Creates a function that resolves the awaited iterable values. Any awaited iterable values that are errors will be thrown.
Restores all mocks registered in the current session that have not already been restored. If an id is provided, it will restore all mocks registered in the session associed with that id that have not already been restored.
Creates a function that returns one of its arguments.
Creates a function that returns its arguments or a subset of them. If end is specified, it will return arguments up to but not including the end.
Creates a function that returns the iterable values. Any iterable values that are errors will be thrown.
Creates a function that returns the instance the method was called on.
| (new (...args: Args) => Self)
| Self,
Wraps a function or instance method with a Spy.
Replaces an instance method with a Stub.
Interfaces
A constructor wrapper that records all calls made to it.
- calls: SpyCall<Self, Args, Self>[]
Information about calls made to the function or instance method.
- new(...args: Args): SelfNo documentation available
- original: new (...args: Args) => Self
The function that is being spied on.
- restore(): void
If spying on an instance method, this restores the original instance method.
- restored: boolean
Whether or not the original instance method has been restored.
Call information recorded by a spy.
- args: [...Args, ...unknown[]]
Arguments passed to a function when called.
- error: { Class?: new (...args: any[]) => Error; msgIncludes?: string; }No documentation available
- returned: Return
The value that was returned by a function. If you expect a promise to reject, expect error instead.
- self: Self
The instance that a method was called on.
A function or instance method wrapper that records all calls made to it.
- calls: SpyCall<Self, Args, Return>[]
Information about calls made to the function or instance method.
- original: () => Returnthis: Self,...args: Args
The function that is being spied on.
- restore(): void
If spying on an instance method, this restores the original instance method.
- restored: boolean
Whether or not the original instance method has been restored.
Call information recorded by a spy.