Nano Spy
June 19, 2026 · View on GitHub
A tiny Node.js library to spy and mock methods in tests with great TypeScript support.
It will take only 6 KB
in your node_modules and have 0 dependencies.
import { spyOn, restoreAll } from 'nanospy'
test.after.each(() => {
restoreAll()
})
test('calls increase', () => {
const spy = spyOn(counter, 'increase')
counter.increase(5)
assert.equal(spy.callCount, 1)
assert.equal(spy.calls, [[5]])
})
Usage
using Syntax
In Node.js 24+ (or any runtime with explicit resource management support), you can use the using declaration to automatically restore the spy when it goes out of scope:
import { spyOn } from 'nanospy'
test('calls increase', () => {
using spy = spyOn(counter, 'increase')
counter.increase(5)
assert.equal(spy.callCount, 1)
}) // `counter.increase` is restored automatically here
This removes the need for restoreAll() in an afterEach hook for spies declared with using.
Method Spy
Spy tracks method calls, but do not change method’s behavior.
import { spyOn, restoreAll } from 'nanospy'
test.after.each(() => {
restoreAll()
})
test('calls increase', () => {
const spy = spyOn(counter, 'increase')
counter.increase(5)
assert.is(spy.called, true)
assert.equal(spy.callCount, 1)
assert.equal(spy.calls, [[5]])
assert.equal(spy.results, [true])
})
Mock
Mock change the method’s behavior.
const spy = spyOn(global, 'fetch', async () => {
return {
json: () => ({ posts })
}
})
Or change next function call:
spy.nextResult({ ok: false })
spy.nextError(error)
Functions
spy can be used to track callback calls.
import { spy } from 'nanospy'
const fn = spy()
fn('a', 10)
fn.callCount //=> 1
fn.calls //=> [['a', 10]]
You can pass spy’s callback:
let fn = spy((name: string) => {
console.log(`Hello, ${name}!`)
})
fn('Ivan') //=> Hello, Ivan!
Or change next function call:
fn.nextResult({ ok: false })
fn.nextError(error)
Promises
You can use helpers to test promises:
import { spy } from 'nanospy'
const fn = spy()
let resolve = fn.nextResolve()
fn().then(arg => {
console.log('Resolved ' + arg)
})
await resolve(1) // => Resolved 1
For testing errors, you can use fn.nextReject().
Remocking
You can reassign mocked function with onCall method:
const obj = {
mark: str => str + '!'
}
const spy = spyOn(obj, 'mark')
obj.mark('a')
assert.equal(spy.results, ['a!'])
spy.onCall(str => str + '?')
obj.mark('a')
assert.equal(spy.results, ['a!', 'a?'])