Ninos
Simple stubbing/spying for AVA
Install / Use
/learn @jamiebuilds/NinosREADME
Niños
Simple stubbing/spying for AVA
Example
Setup
const test = require('ninos')(require('ava'));
t.context.stub()
const EventEmitter = require('events');
test('EventEmitter', t => {
let e = new EventEmitter();
let s = t.context.stub();
e.on('event', s);
e.emit('event');
t.is(s.calls.length, 1);
e.emit('event', 'arg');
t.is(s.calls[1].arguments[0], 'arg');
});
t.context.spy()
const api = require('./api');
test('api.getCurrentUser()', t => {
let s = t.context.spy(api, 'request', () => {
return Promise.resolve({ id: 42 });
});
await api.getCurrentUser();
t.deepEqual(s.calls[0].arguments[0], {
method: 'GET',
url: '/api/v1/user',
});
});
Install
yarn add --dev ninos
Usage
ninos()
This method setups the t.context.stub() and t.context.spy() functions. It
hooks into AVA to automatically restore spies after each test.
const test = require('ninos')(require('ava'));
t.context.stub()
Call this method to create a function that you can use in place of any other function (as a callback/etc).
test('example', t => {
let s = t.context.stub(); // [Function]
});
On that function is a calls property which is an array of all the calls you
made.
let s = t.context.stub();
s.call('this', 'arg1', 'arg2');
t.deepEqual(s.calls, [
{ this: 'this', arguments: ['arg1', 'arg2'], return: undefined },
]);
You can optional pass an inner function to be called inside the stub to customize its behavior.
let s = t.context.stub((...args) => {
return 'hello!';
});
s();
t.deepEqual(s.calls, [
{ ..., return: 'hello!' },
]);
If you want to customize the behavior based on the current call you can use
s.calls.
let s = t.context.stub((...args) => {
if (s.calls.length === 0) return 'one';
if (s.calls.length === 1) return 'two';
if (s.calls.length === 2) return 'three';
throw new Error('too many calls!');
});
t.is(s(), 'one');
t.is(s(), 'two');
t.is(s(), 'three');
t.throws(() => s()); // Error: too many calls!
t.context.spy()
If you need to write tests against a method on an object, you should use a spy instead of a stub.
let method = () => 'hello from method';
let object = { method };
let s = t.context.spy(object, 'method');
Just like stubs, spies have a calls property.
let s = t.context.spy(object, 'method');
object.method.call('this', 'arg1', 'arg2');
t.deepEqual(s.calls, [
{ this: 'this', arguments: ['arg1', 'arg2'], return: 'hello from method'; },
]);
By default, spies will call the original function. If you want to customize the behavior you can pass your own inner function.
let s = t.context.spy(object, 'method', (...args) => {
return 'hello from spy'
});
object.method();
t.deepEqual(s.calls, [
{ ..., return: 'hello from spy' },
]);
If you still want access to the original function you can find it on
s.original.
let s = t.context.spy(object, 'method', (...args) => {
return s.original(...args) + ' and hello from spy';
});
object.method();
t.deepEqual(s.calls, [
{ ..., return: 'hello from method and hello from spy' },
]);
Spies will automatically be restored at the end of your test, but if you want to do it yourself:
let s = test.context.spy(object, 'method');
object.method = s.original;
API
Here is the basic API interface:
type Call =
| { this: any, arguments: Array<any>, return: any }
| { this: any, arguments: Array<any>, throw: any }; // when an error was thrown
type Stub = Function & { calls: Array<Call> };
type Spy = Function & { calls: Array<Call>, original: Function };
Design
Niños tries to keep things as miminal as possible. So it avoids APIs like:
let s = t.context.stub();
s.onCall(0).returns('ret1');
s.onCall(1).returns('ret2');
And:
t.toHaveBeenCalledWith(s, 'arg1', 'arg2');
Instead you should write tests like this:
test('example', t => {
let s = t.context.stub(() => {
if (s.calls.length === 0) return 'ret1';
if (s.calls.length === 1) return 'ret2';
});
t.deepEqual(s.calls[0], ['arg1', 'arg2']);
});
This is ultimately more flexible and doesn't end up with dozens of weird one-off APIs for you to memorize.
If you prefer the former, Sinon is the library for you.
Note: This is part of a proposal to add stubs/spies to AVA itself
Related Skills
gh-issues
332.3kFetch GitHub issues, spawn sub-agents to implement fixes and open PRs, then monitor and address PR review comments. Usage: /gh-issues [owner/repo] [--label bug] [--limit 5] [--milestone v1.0] [--assignee @me] [--fork user/repo] [--watch] [--interval 5] [--reviews-only] [--cron] [--dry-run] [--model glm-5] [--notify-channel -1002381931352]
node-connect
332.3kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
81.7kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
Writing Hookify Rules
81.7kThis skill should be used when the user asks to "create a hookify rule", "write a hook rule", "configure hookify", "add a hookify rule", or needs guidance on hookify rule syntax and patterns.
