Sinon-Chai: Sinon.JS Assertions for Chai
Sinon-Chai is a testing utility that integrates the Sinon.JS mocking framework with the Chai assertion library, allowing developers to write more expressive and readable assertions for spies, stubs, and mocks. Instead of using Sinon's direct assertion methods (`sinon.assert.calledWith`) or awkward Chai property checks, Sinon-Chai extends Chai's `should` and `expect` interfaces to provide natural language assertions like `expect(mySpy).to.have.been.calledWith('foo')`. The current stable version is 4.0.1, which supports Chai v5 and v6, and Sinon v4+. The library maintains an active release cadence, typically updating to support new major versions of its peer dependencies, Chai and Sinon. Key differentiators include its seamless integration into the Chai assertion chain, improving test readability and developer experience by providing a unified assertion style across a test suite. It's widely used in JavaScript testing environments for both Node.js and browser applications.
Common errors
-
TypeError: Cannot read properties of undefined (reading 'should') OR TypeError: expect is not a function
cause Chai's assertion interfaces (`should` or `expect`) are not correctly loaded or activated before using Sinon-Chai assertions.fixEnsure you have `import { expect, use } from 'chai';` and `use(sinonChai);` at the top of your test file. For the `should` style, also include `import 'chai/register-should';` or call `chai.should();`. -
AssertionError: Expected spy to be called once, but it was not. (or similar for `calledWith`, `returned`, etc.)
cause The Sinon spy, stub, or mock was either not invoked, called with different arguments, or the test subject did not behave as expected.fixReview your test setup and the code under test. Ensure the spy is correctly injected and that the execution path actually triggers the spy. Use `mySpy.getCalls()` or `console.log(mySpy.args)` to inspect the spy's behavior. -
Error [ERR_REQUIRE_ESM]: require() of ES Module .../node_modules/sinon-chai/index.js from ... is not supported.
cause You are attempting to import `sinon-chai` using CommonJS `require()` syntax in a project configured for ESM, or Node.js's module resolution is treating it as an ESM module when your code is CJS.fixUse ESM import syntax: `import sinonChai from 'sinon-chai';`. If your project is CommonJS, you might need to ensure compatibility or check for older versions that explicitly supported CJS without such issues.
Warnings
- breaking Version 3.0.0 introduced breaking changes, requiring Node.js 4+, Sinon 4+, and Chai 4+.
- breaking Version 4.0.0 added support for Chai v5, and 4.0.1 added support for Chai v6. Ensure your Chai version is compatible with your `sinon-chai` version based on the peer dependencies.
- gotcha When using `always` assertions (e.g., `alwaysCalledWith`), the `.always` property must be placed directly after `should` or `to` (e.g., `spy.should.always.have.been.calledWith`). Incorrect placement like `spy.should.have.been.alwaysCalledWith` will not work.
- gotcha Sinon-Chai does not directly provide assertions for `Sinon.assert.match`. If you need to assert against complex argument matching, consider using `chai-samsam` or manual checks.
Install
-
npm install sinon-chai -
yarn add sinon-chai -
pnpm add sinon-chai
Imports
- sinonChai
const sinonChai = require('sinon-chai');import sinonChai from 'sinon-chai';
- use (Chai method)
chai.use(sinonChai); // if `chai` itself is imported via CJS `require`
import { use, expect } from 'chai'; use(sinonChai); - should (Chai style)
import 'chai/register-should'; // or `chai.should()` // ... then later: mySpy.should.have.been.calledOnce;
Quickstart
import { expect, use } from 'chai';
import * as sinon from 'sinon';
import sinonChai from 'sinon-chai';
// Initialize Chai with sinon-chai plugin
use(sinonChai);
describe('MyService with Sinon-Chai', () => {
let mySpy: sinon.SinonSpy;
beforeEach(() => {
// Create a new Sinon spy before each test
mySpy = sinon.spy();
});
afterEach(() => {
// Restore the spy after each test to prevent side effects
mySpy.restore();
});
it('should call the spy once with the expected argument', () => {
const serviceMethod = (param: string) => {
mySpy(param);
};
serviceMethod('first-call');
// Use sinon-chai assertions
expect(mySpy).to.have.been.calledOnce;
expect(mySpy).to.have.been.calledWith('first-call');
expect(mySpy).to.not.have.been.calledWith('wrong-argument');
});
it('should not call the spy if a condition is not met', () => {
const serviceMethodConditional = (shouldExecute: boolean) => {
if (shouldExecute) {
mySpy('executed');
}
};
serviceMethodConditional(false);
expect(mySpy).to.not.have.been.called;
expect(mySpy).to.have.callCount(0);
});
});