or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/npm-sinon-chai

Extends Chai with assertions for the Sinon.JS mocking framework.

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/sinon-chai@4.0.x

To install, run

npx @tessl/cli install tessl/npm-sinon-chai@4.0.0

index.mddocs/

Sinon-Chai

Sinon-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.

Package Information

  • Package Name: sinon-chai
  • Package Type: npm
  • Language: JavaScript (ES Modules)
  • Installation: npm install --save-dev sinon-chai

Core Imports

import 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.

Basic Usage

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;
    });
});

Architecture

Sinon-Chai is built around the Chai plugin architecture and integrates with Sinon.JS spies, stubs, and mocks:

  • Plugin Registration: The default export is a Chai plugin function that registers all assertion methods with Chai's prototype
  • Assertion Extension: Extends Chai's Assertion.prototype with Sinon-specific methods and properties
  • Spy Detection: Uses internal functions to detect valid Sinon spies and spy calls before applying assertions
  • Message Generation: Leverages Sinon's built-in printf-style message formatting for clear assertion feedback
  • Always Modifier: Implements a special always flag that changes assertion behavior to check all calls rather than any call
  • Method Types: Supports three types of assertions:
    • Properties (called, calledOnce, etc.) - Simple boolean checks
    • Boolean Methods (callCount(n)) - Methods that take parameters for comparison
    • Spy Methods (calledWith, returned, etc.) - Methods that delegate to Sinon's spy methods

Capabilities

Plugin Registration

The 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;
}

Call Detection Assertions

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;

Constructor Call Assertions

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 fail

Call Order Assertions

Assertions 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);

Context (this) Assertions

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 context

Argument Assertions

Assertions 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);

Return Value Assertions

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 value

Exception Assertions

Assertions 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 throw

Always Modifier

The 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");

Types

/**
 * 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;
}

Error Handling

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);

Compatibility

  • Chai: Requires Chai 5.x or 6.x
  • Sinon: Requires Sinon 4.0.0 or higher
  • Node.js: ES Module support required
  • Browsers: Modern browsers with ES Module support
  • Assertion Styles: Compatible with both Chai's should and expect interfaces
  • Negation: All assertions support Chai's .not negation modifier