Extends Chai with assertions for the Sinon.JS mocking framework.
npx @tessl/cli install tessl/npm-sinon-chai@4.0.00
# Sinon-Chai
1
2
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.
3
4
## Package Information
5
6
- **Package Name**: sinon-chai
7
- **Package Type**: npm
8
- **Language**: JavaScript (ES Modules)
9
- **Installation**: `npm install --save-dev sinon-chai`
10
11
## Core Imports
12
13
```javascript
14
import sinonChai from "sinon-chai";
15
import * as chai from "chai";
16
17
chai.use(sinonChai);
18
```
19
20
For CommonJS environments:
21
22
```javascript
23
const sinonChai = require("sinon-chai");
24
const chai = require("chai");
25
26
chai.use(sinonChai);
27
```
28
29
Note: The main export is a function that takes two parameters: the Chai constructor and Chai's utility functions.
30
31
## Basic Usage
32
33
```javascript
34
import * as chai from "chai";
35
import sinon from "sinon";
36
import sinonChai from "sinon-chai";
37
38
// Setup chai with sinon-chai plugin
39
chai.use(sinonChai);
40
41
// For should-style assertions
42
chai.should();
43
44
// For expect-style assertions
45
const { expect } = chai;
46
47
function hello(name, callback) {
48
callback("hello " + name);
49
}
50
51
describe("hello", function () {
52
it("should call callback with correct greeting", function () {
53
const callback = sinon.spy();
54
55
hello("world", callback);
56
57
// Using should syntax
58
callback.should.have.been.calledWith("hello world");
59
callback.should.have.been.calledOnce;
60
61
// Using expect syntax
62
expect(callback).to.have.been.calledWith("hello world");
63
expect(callback).to.have.been.calledOnce;
64
});
65
});
66
```
67
68
## Architecture
69
70
Sinon-Chai is built around the Chai plugin architecture and integrates with Sinon.JS spies, stubs, and mocks:
71
72
- **Plugin Registration**: The default export is a Chai plugin function that registers all assertion methods with Chai's prototype
73
- **Assertion Extension**: Extends Chai's `Assertion.prototype` with Sinon-specific methods and properties
74
- **Spy Detection**: Uses internal functions to detect valid Sinon spies and spy calls before applying assertions
75
- **Message Generation**: Leverages Sinon's built-in printf-style message formatting for clear assertion feedback
76
- **Always Modifier**: Implements a special `always` flag that changes assertion behavior to check all calls rather than any call
77
- **Method Types**: Supports three types of assertions:
78
- **Properties** (`called`, `calledOnce`, etc.) - Simple boolean checks
79
- **Boolean Methods** (`callCount(n)`) - Methods that take parameters for comparison
80
- **Spy Methods** (`calledWith`, `returned`, etc.) - Methods that delegate to Sinon's spy methods
81
82
## Capabilities
83
84
### Plugin Registration
85
86
The main export is a Chai plugin function that registers all Sinon-Chai assertions.
87
88
```javascript { .api }
89
/**
90
* Sinon-Chai plugin function for registering with Chai
91
* @param chai - The Chai constructor function
92
* @param utils - Chai's utility functions including addProperty and addMethod
93
*/
94
export default function sinonChai(
95
chai: ChaiStatic,
96
utils: ChaiUtils
97
): void;
98
99
interface ChaiStatic {
100
Assertion: ChaiAssertion;
101
}
102
103
interface ChaiUtils {
104
addProperty(ctx: any, name: string, getter: Function): void;
105
addMethod(ctx: any, name: string, method: Function): void;
106
flag(obj: any, key: string, value?: any): any;
107
inspect(obj: any): string;
108
}
109
```
110
111
### Call Detection Assertions
112
113
Assertions for detecting whether spies, stubs, or mocks have been called.
114
115
```javascript { .api }
116
/**
117
* Assert that a spy has been called at least once
118
* Usage: spy.should.have.been.called
119
*/
120
interface CalledAssertion {
121
called: ChaiAssertion;
122
}
123
124
/**
125
* Assert that a spy has been called exactly n times
126
* Usage: spy.should.have.callCount(3)
127
*/
128
interface CallCountAssertion {
129
callCount(count: number): ChaiAssertion;
130
}
131
132
/**
133
* Assert that a spy has been called exactly once
134
* Usage: spy.should.have.been.calledOnce
135
*/
136
interface CalledOnceAssertion {
137
calledOnce: ChaiAssertion;
138
}
139
140
/**
141
* Assert that a spy has been called exactly twice
142
* Usage: spy.should.have.been.calledTwice
143
*/
144
interface CalledTwiceAssertion {
145
calledTwice: ChaiAssertion;
146
}
147
148
/**
149
* Assert that a spy has been called exactly thrice
150
* Usage: spy.should.have.been.calledThrice
151
*/
152
interface CalledThriceAssertion {
153
calledThrice: ChaiAssertion;
154
}
155
```
156
157
**Usage Examples:**
158
159
```javascript
160
const spy = sinon.spy();
161
162
spy();
163
spy();
164
165
spy.should.have.been.called;
166
spy.should.have.callCount(2);
167
spy.should.have.been.calledTwice;
168
169
// Negation
170
spy.should.not.have.been.calledOnce;
171
spy.should.not.have.been.calledThrice;
172
```
173
174
### Constructor Call Assertions
175
176
Assertions for detecting calls made with the `new` keyword.
177
178
```javascript { .api }
179
/**
180
* Assert that a spy was called with the new keyword
181
* Usage: spy.should.have.been.calledWithNew
182
* Always variant: spy.should.always.have.been.calledWithNew
183
*/
184
interface CalledWithNewAssertion {
185
calledWithNew: ChaiAssertion;
186
}
187
```
188
189
**Usage Examples:**
190
191
```javascript
192
const ConstructorSpy = sinon.spy();
193
194
new ConstructorSpy();
195
ConstructorSpy("without new");
196
197
ConstructorSpy.should.have.been.calledWithNew;
198
199
// For checking all calls
200
ConstructorSpy.should.always.have.been.calledWithNew; // Will fail
201
```
202
203
### Call Order Assertions
204
205
Assertions for verifying the order in which spies were called.
206
207
```javascript { .api }
208
/**
209
* Assert that spy1 was called before spy2
210
* Usage: spy1.should.have.been.calledBefore(spy2)
211
*/
212
interface CalledBeforeAssertion {
213
calledBefore(anotherSpy: SinonSpy): ChaiAssertion;
214
}
215
216
/**
217
* Assert that spy1 was called after spy2
218
* Usage: spy1.should.have.been.calledAfter(spy2)
219
*/
220
interface CalledAfterAssertion {
221
calledAfter(anotherSpy: SinonSpy): ChaiAssertion;
222
}
223
224
/**
225
* Assert that spy1 was called immediately before spy2
226
* Usage: spy1.should.have.been.calledImmediatelyBefore(spy2)
227
*/
228
interface CalledImmediatelyBeforeAssertion {
229
calledImmediatelyBefore(anotherSpy: SinonSpy): ChaiAssertion;
230
}
231
232
/**
233
* Assert that spy1 was called immediately after spy2
234
* Usage: spy1.should.have.been.calledImmediatelyAfter(spy2)
235
*/
236
interface CalledImmediatelyAfterAssertion {
237
calledImmediatelyAfter(anotherSpy: SinonSpy): ChaiAssertion;
238
}
239
```
240
241
**Usage Examples:**
242
243
```javascript
244
const spy1 = sinon.spy();
245
const spy2 = sinon.spy();
246
247
spy1();
248
spy2();
249
250
spy1.should.have.been.calledBefore(spy2);
251
spy2.should.have.been.calledAfter(spy1);
252
spy1.should.have.been.calledImmediatelyBefore(spy2);
253
spy2.should.have.been.calledImmediatelyAfter(spy1);
254
```
255
256
### Context (this) Assertions
257
258
Assertions for verifying the context (this value) with which spies were called.
259
260
```javascript { .api }
261
/**
262
* Assert that a spy was called with the specified context (this value)
263
* Usage: spy.should.have.been.calledOn(context)
264
* Always variant: spy.should.always.have.been.calledOn(context)
265
*/
266
interface CalledOnAssertion {
267
calledOn(context: any): ChaiAssertion;
268
}
269
```
270
271
**Usage Examples:**
272
273
```javascript
274
const spy = sinon.spy();
275
const obj = { method: spy };
276
277
obj.method();
278
spy.call(obj);
279
spy.call({ other: true });
280
281
spy.should.have.been.calledOn(obj);
282
spy.should.not.always.have.been.calledOn(obj); // Because third call used different context
283
```
284
285
### Argument Assertions
286
287
Assertions for verifying the arguments passed to spies.
288
289
```javascript { .api }
290
/**
291
* Assert that a spy was called with the specified arguments (partial match)
292
* Usage: spy.should.have.been.calledWith(arg1, arg2)
293
* Always variant: spy.should.always.have.been.calledWith(arg1, arg2)
294
*/
295
interface CalledWithAssertion {
296
calledWith(...args: any[]): ChaiAssertion;
297
}
298
299
/**
300
* Assert that a spy was called exactly once with the specified arguments
301
* Usage: spy.should.have.been.calledOnceWith(arg1, arg2)
302
*/
303
interface CalledOnceWithAssertion {
304
calledOnceWith(...args: any[]): ChaiAssertion;
305
}
306
307
/**
308
* Assert that a spy was called with exactly the specified arguments (exact match)
309
* Usage: spy.should.have.been.calledWithExactly(arg1, arg2)
310
* Always variant: spy.should.always.have.been.calledWithExactly(arg1, arg2)
311
*/
312
interface CalledWithExactlyAssertion {
313
calledWithExactly(...args: any[]): ChaiAssertion;
314
}
315
316
/**
317
* Assert that a spy was called exactly once with exactly the specified arguments
318
* Usage: spy.should.have.been.calledOnceWithExactly(arg1, arg2)
319
*/
320
interface CalledOnceWithExactlyAssertion {
321
calledOnceWithExactly(...args: any[]): ChaiAssertion;
322
}
323
324
/**
325
* Assert that a spy was called with arguments matching the specified Sinon matchers
326
* Usage: spy.should.have.been.calledWithMatch(sinon.match.string, sinon.match.number)
327
* Always variant: spy.should.always.have.been.calledWithMatch(...matchers)
328
*/
329
interface CalledWithMatchAssertion {
330
calledWithMatch(...matchers: any[]): ChaiAssertion;
331
}
332
```
333
334
**Usage Examples:**
335
336
```javascript
337
const spy = sinon.spy();
338
339
spy("hello", "world", "extra");
340
spy("foo", "bar");
341
342
// Partial matching - passes because "hello", "world" are present
343
spy.should.have.been.calledWith("hello", "world");
344
345
// Exact matching - checks exact argument list
346
spy.should.have.been.calledWithExactly("hello", "world", "extra");
347
348
// Once variants - for single call verification
349
const onceSpy = sinon.spy();
350
onceSpy("single", "call");
351
onceSpy.should.have.been.calledOnceWith("single", "call");
352
onceSpy.should.have.been.calledOnceWithExactly("single", "call");
353
354
// Matcher-based assertions
355
spy.should.have.been.calledWithMatch(sinon.match.string, sinon.match.string);
356
```
357
358
### Return Value Assertions
359
360
Assertions for verifying values returned by spies.
361
362
```javascript { .api }
363
/**
364
* Assert that a spy returned the specified value
365
* Usage: spy.should.have.returned(value)
366
* Always variant: spy.should.have.always.returned(value)
367
*/
368
interface ReturnedAssertion {
369
returned(value: any): ChaiAssertion;
370
}
371
```
372
373
**Usage Examples:**
374
375
```javascript
376
const stub = sinon.stub();
377
stub.onFirstCall().returns("first");
378
stub.onSecondCall().returns("second");
379
380
stub();
381
stub();
382
383
stub.should.have.returned("first");
384
stub.should.have.returned("second");
385
stub.should.not.have.always.returned("first"); // Because second call returned different value
386
```
387
388
### Exception Assertions
389
390
Assertions for verifying exceptions thrown by spies.
391
392
```javascript { .api }
393
/**
394
* Assert that a spy threw an exception, optionally of a specific type
395
* Usage: spy.should.have.thrown()
396
* Usage: spy.should.have.thrown(Error)
397
* Usage: spy.should.have.thrown('TypeError')
398
* Always variant: spy.should.have.always.thrown(errorType)
399
*/
400
interface ThrownAssertion {
401
thrown(errorType?: any): ChaiAssertion;
402
}
403
```
404
405
**Usage Examples:**
406
407
```javascript
408
const stub = sinon.stub();
409
stub.onFirstCall().throws(new TypeError("Invalid argument"));
410
stub.onSecondCall().returns("success");
411
412
stub(); // Throws
413
stub(); // Returns
414
415
stub.should.have.thrown();
416
stub.should.have.thrown(TypeError);
417
stub.should.have.thrown("TypeError");
418
stub.should.not.have.always.thrown(); // Because second call didn't throw
419
```
420
421
### Always Modifier
422
423
The `always` modifier changes assertions to require that the condition holds for ALL calls to the spy.
424
425
```javascript { .api }
426
/**
427
* Modifier that requires assertions to hold for all calls to a spy
428
* Usage: spy.should.always.have.been.calledWith(arg)
429
*/
430
interface AlwaysModifier {
431
always: ChaiAssertion;
432
}
433
```
434
435
**Usage Examples:**
436
437
```javascript
438
const spy = sinon.spy();
439
440
spy("consistent");
441
spy("consistent");
442
spy("different");
443
444
// These will pass
445
spy.should.have.been.calledWith("consistent");
446
spy.should.have.been.calledWith("different");
447
448
// This will fail because not ALL calls used "consistent"
449
spy.should.not.always.have.been.calledWith("consistent");
450
```
451
452
## Types
453
454
```javascript { .api }
455
/**
456
* Sinon spy interface (simplified for type reference)
457
*/
458
interface SinonSpy {
459
(...args: any[]): any;
460
getCall(index: number): SinonSpyCall;
461
calledWith(...args: any[]): boolean;
462
calledWithExactly(...args: any[]): boolean;
463
called: boolean;
464
callCount: number;
465
calledOnce: boolean;
466
calledTwice: boolean;
467
calledThrice: boolean;
468
}
469
470
/**
471
* Sinon spy call interface (simplified for type reference)
472
*/
473
interface SinonSpyCall {
474
proxy: SinonSpy;
475
calledWith(...args: any[]): boolean;
476
calledWithExactly(...args: any[]): boolean;
477
}
478
479
/**
480
* Chai assertion interface (extended by Sinon-Chai)
481
*/
482
interface ChaiAssertion {
483
not: ChaiAssertion;
484
have: ChaiAssertion;
485
been: ChaiAssertion;
486
always: ChaiAssertion;
487
Assertion: Function;
488
}
489
```
490
491
## Error Handling
492
493
Sinon-Chai throws `TypeError` when assertions are used on objects that are not Sinon spies or spy calls:
494
495
```javascript
496
const notASpy = {};
497
498
// This will throw TypeError: [object Object] is not a spy or a call to a spy!
499
expect(() => {
500
expect(notASpy).to.have.been.called;
501
}).to.throw(TypeError);
502
```
503
504
## Compatibility
505
506
- **Chai**: Requires Chai 5.x or 6.x
507
- **Sinon**: Requires Sinon 4.0.0 or higher
508
- **Node.js**: ES Module support required
509
- **Browsers**: Modern browsers with ES Module support
510
- **Assertion Styles**: Compatible with both Chai's `should` and `expect` interfaces
511
- **Negation**: All assertions support Chai's `.not` negation modifier