Extends Chai with assertions for the Sinon.JS mocking framework.
npx @tessl/cli install tessl/npm-sinon-chai@4.0.0Sinon-Chai provides a set of custom assertions for using the Sinon.JS spy, stub, and mocking framework with the Chai assertion library. It extends Chai with fluent, readable assertions for testing function calls, arguments, return values, and exceptions.
npm install --save-dev sinon-chaiimport sinonChai from "sinon-chai";
import * as chai from "chai";
chai.use(sinonChai);For CommonJS environments:
const sinonChai = require("sinon-chai");
const chai = require("chai");
chai.use(sinonChai);Note: The main export is a function that takes two parameters: the Chai constructor and Chai's utility functions.
import * as chai from "chai";
import sinon from "sinon";
import sinonChai from "sinon-chai";
// Setup chai with sinon-chai plugin
chai.use(sinonChai);
// For should-style assertions
chai.should();
// For expect-style assertions
const { expect } = chai;
function hello(name, callback) {
callback("hello " + name);
}
describe("hello", function () {
it("should call callback with correct greeting", function () {
const callback = sinon.spy();
hello("world", callback);
// Using should syntax
callback.should.have.been.calledWith("hello world");
callback.should.have.been.calledOnce;
// Using expect syntax
expect(callback).to.have.been.calledWith("hello world");
expect(callback).to.have.been.calledOnce;
});
});Sinon-Chai is built around the Chai plugin architecture and integrates with Sinon.JS spies, stubs, and mocks:
Assertion.prototype with Sinon-specific methods and propertiesalways flag that changes assertion behavior to check all calls rather than any callcalled, calledOnce, etc.) - Simple boolean checkscallCount(n)) - Methods that take parameters for comparisoncalledWith, returned, etc.) - Methods that delegate to Sinon's spy methodsThe main export is a Chai plugin function that registers all Sinon-Chai assertions.
/**
* Sinon-Chai plugin function for registering with Chai
* @param chai - The Chai constructor function
* @param utils - Chai's utility functions including addProperty and addMethod
*/
export default function sinonChai(
chai: ChaiStatic,
utils: ChaiUtils
): void;
interface ChaiStatic {
Assertion: ChaiAssertion;
}
interface ChaiUtils {
addProperty(ctx: any, name: string, getter: Function): void;
addMethod(ctx: any, name: string, method: Function): void;
flag(obj: any, key: string, value?: any): any;
inspect(obj: any): string;
}Assertions for detecting whether spies, stubs, or mocks have been called.
/**
* Assert that a spy has been called at least once
* Usage: spy.should.have.been.called
*/
interface CalledAssertion {
called: ChaiAssertion;
}
/**
* Assert that a spy has been called exactly n times
* Usage: spy.should.have.callCount(3)
*/
interface CallCountAssertion {
callCount(count: number): ChaiAssertion;
}
/**
* Assert that a spy has been called exactly once
* Usage: spy.should.have.been.calledOnce
*/
interface CalledOnceAssertion {
calledOnce: ChaiAssertion;
}
/**
* Assert that a spy has been called exactly twice
* Usage: spy.should.have.been.calledTwice
*/
interface CalledTwiceAssertion {
calledTwice: ChaiAssertion;
}
/**
* Assert that a spy has been called exactly thrice
* Usage: spy.should.have.been.calledThrice
*/
interface CalledThriceAssertion {
calledThrice: ChaiAssertion;
}Usage Examples:
const spy = sinon.spy();
spy();
spy();
spy.should.have.been.called;
spy.should.have.callCount(2);
spy.should.have.been.calledTwice;
// Negation
spy.should.not.have.been.calledOnce;
spy.should.not.have.been.calledThrice;Assertions for detecting calls made with the new keyword.
/**
* Assert that a spy was called with the new keyword
* Usage: spy.should.have.been.calledWithNew
* Always variant: spy.should.always.have.been.calledWithNew
*/
interface CalledWithNewAssertion {
calledWithNew: ChaiAssertion;
}Usage Examples:
const ConstructorSpy = sinon.spy();
new ConstructorSpy();
ConstructorSpy("without new");
ConstructorSpy.should.have.been.calledWithNew;
// For checking all calls
ConstructorSpy.should.always.have.been.calledWithNew; // Will failAssertions for verifying the order in which spies were called.
/**
* Assert that spy1 was called before spy2
* Usage: spy1.should.have.been.calledBefore(spy2)
*/
interface CalledBeforeAssertion {
calledBefore(anotherSpy: SinonSpy): ChaiAssertion;
}
/**
* Assert that spy1 was called after spy2
* Usage: spy1.should.have.been.calledAfter(spy2)
*/
interface CalledAfterAssertion {
calledAfter(anotherSpy: SinonSpy): ChaiAssertion;
}
/**
* Assert that spy1 was called immediately before spy2
* Usage: spy1.should.have.been.calledImmediatelyBefore(spy2)
*/
interface CalledImmediatelyBeforeAssertion {
calledImmediatelyBefore(anotherSpy: SinonSpy): ChaiAssertion;
}
/**
* Assert that spy1 was called immediately after spy2
* Usage: spy1.should.have.been.calledImmediatelyAfter(spy2)
*/
interface CalledImmediatelyAfterAssertion {
calledImmediatelyAfter(anotherSpy: SinonSpy): ChaiAssertion;
}Usage Examples:
const spy1 = sinon.spy();
const spy2 = sinon.spy();
spy1();
spy2();
spy1.should.have.been.calledBefore(spy2);
spy2.should.have.been.calledAfter(spy1);
spy1.should.have.been.calledImmediatelyBefore(spy2);
spy2.should.have.been.calledImmediatelyAfter(spy1);Assertions for verifying the context (this value) with which spies were called.
/**
* Assert that a spy was called with the specified context (this value)
* Usage: spy.should.have.been.calledOn(context)
* Always variant: spy.should.always.have.been.calledOn(context)
*/
interface CalledOnAssertion {
calledOn(context: any): ChaiAssertion;
}Usage Examples:
const spy = sinon.spy();
const obj = { method: spy };
obj.method();
spy.call(obj);
spy.call({ other: true });
spy.should.have.been.calledOn(obj);
spy.should.not.always.have.been.calledOn(obj); // Because third call used different contextAssertions for verifying the arguments passed to spies.
/**
* Assert that a spy was called with the specified arguments (partial match)
* Usage: spy.should.have.been.calledWith(arg1, arg2)
* Always variant: spy.should.always.have.been.calledWith(arg1, arg2)
*/
interface CalledWithAssertion {
calledWith(...args: any[]): ChaiAssertion;
}
/**
* Assert that a spy was called exactly once with the specified arguments
* Usage: spy.should.have.been.calledOnceWith(arg1, arg2)
*/
interface CalledOnceWithAssertion {
calledOnceWith(...args: any[]): ChaiAssertion;
}
/**
* Assert that a spy was called with exactly the specified arguments (exact match)
* Usage: spy.should.have.been.calledWithExactly(arg1, arg2)
* Always variant: spy.should.always.have.been.calledWithExactly(arg1, arg2)
*/
interface CalledWithExactlyAssertion {
calledWithExactly(...args: any[]): ChaiAssertion;
}
/**
* Assert that a spy was called exactly once with exactly the specified arguments
* Usage: spy.should.have.been.calledOnceWithExactly(arg1, arg2)
*/
interface CalledOnceWithExactlyAssertion {
calledOnceWithExactly(...args: any[]): ChaiAssertion;
}
/**
* Assert that a spy was called with arguments matching the specified Sinon matchers
* Usage: spy.should.have.been.calledWithMatch(sinon.match.string, sinon.match.number)
* Always variant: spy.should.always.have.been.calledWithMatch(...matchers)
*/
interface CalledWithMatchAssertion {
calledWithMatch(...matchers: any[]): ChaiAssertion;
}Usage Examples:
const spy = sinon.spy();
spy("hello", "world", "extra");
spy("foo", "bar");
// Partial matching - passes because "hello", "world" are present
spy.should.have.been.calledWith("hello", "world");
// Exact matching - checks exact argument list
spy.should.have.been.calledWithExactly("hello", "world", "extra");
// Once variants - for single call verification
const onceSpy = sinon.spy();
onceSpy("single", "call");
onceSpy.should.have.been.calledOnceWith("single", "call");
onceSpy.should.have.been.calledOnceWithExactly("single", "call");
// Matcher-based assertions
spy.should.have.been.calledWithMatch(sinon.match.string, sinon.match.string);Assertions for verifying values returned by spies.
/**
* Assert that a spy returned the specified value
* Usage: spy.should.have.returned(value)
* Always variant: spy.should.have.always.returned(value)
*/
interface ReturnedAssertion {
returned(value: any): ChaiAssertion;
}Usage Examples:
const stub = sinon.stub();
stub.onFirstCall().returns("first");
stub.onSecondCall().returns("second");
stub();
stub();
stub.should.have.returned("first");
stub.should.have.returned("second");
stub.should.not.have.always.returned("first"); // Because second call returned different valueAssertions for verifying exceptions thrown by spies.
/**
* Assert that a spy threw an exception, optionally of a specific type
* Usage: spy.should.have.thrown()
* Usage: spy.should.have.thrown(Error)
* Usage: spy.should.have.thrown('TypeError')
* Always variant: spy.should.have.always.thrown(errorType)
*/
interface ThrownAssertion {
thrown(errorType?: any): ChaiAssertion;
}Usage Examples:
const stub = sinon.stub();
stub.onFirstCall().throws(new TypeError("Invalid argument"));
stub.onSecondCall().returns("success");
stub(); // Throws
stub(); // Returns
stub.should.have.thrown();
stub.should.have.thrown(TypeError);
stub.should.have.thrown("TypeError");
stub.should.not.have.always.thrown(); // Because second call didn't throwThe always modifier changes assertions to require that the condition holds for ALL calls to the spy.
/**
* Modifier that requires assertions to hold for all calls to a spy
* Usage: spy.should.always.have.been.calledWith(arg)
*/
interface AlwaysModifier {
always: ChaiAssertion;
}Usage Examples:
const spy = sinon.spy();
spy("consistent");
spy("consistent");
spy("different");
// These will pass
spy.should.have.been.calledWith("consistent");
spy.should.have.been.calledWith("different");
// This will fail because not ALL calls used "consistent"
spy.should.not.always.have.been.calledWith("consistent");/**
* Sinon spy interface (simplified for type reference)
*/
interface SinonSpy {
(...args: any[]): any;
getCall(index: number): SinonSpyCall;
calledWith(...args: any[]): boolean;
calledWithExactly(...args: any[]): boolean;
called: boolean;
callCount: number;
calledOnce: boolean;
calledTwice: boolean;
calledThrice: boolean;
}
/**
* Sinon spy call interface (simplified for type reference)
*/
interface SinonSpyCall {
proxy: SinonSpy;
calledWith(...args: any[]): boolean;
calledWithExactly(...args: any[]): boolean;
}
/**
* Chai assertion interface (extended by Sinon-Chai)
*/
interface ChaiAssertion {
not: ChaiAssertion;
have: ChaiAssertion;
been: ChaiAssertion;
always: ChaiAssertion;
Assertion: Function;
}Sinon-Chai throws TypeError when assertions are used on objects that are not Sinon spies or spy calls:
const notASpy = {};
// This will throw TypeError: [object Object] is not a spy or a call to a spy!
expect(() => {
expect(notASpy).to.have.been.called;
}).to.throw(TypeError);should and expect interfaces.not negation modifier