--- url: /concepts/spies.md description: >- Test spies that record arguments, return values, and exceptions for all calls. Wrap existing methods or create anonymous spies. --- # Spies ## Introduction ::: warning Consider Using Fakes Instead [Fakes][fakes] are the recommended alternative to spies. They provide the same functionality with a simpler, more consistent API. Consider using `sinon.fake()` instead of `sinon.spy()` for new code. ::: ## What is a test spy? A test spy is a function that records arguments, return value, the value of `this` and exception thrown (if any) for all its [calls][call]. There are two types of spies: Some are anonymous functions, while others wrap methods that already exist in the system under test. ## Creating a spy as an anonymous function When the behavior of the spied-on function is not under test, you can use an anonymous function spy. The spy won't do anything except record information about its [calls][call]. A common use case for this type of spy is testing how a function handles a callback, as in the following simplified example: ```js import t from "tap"; import sinon from "sinon"; t.test("anonymous spy records calls and can be asserted", (t) => { const spy = sinon.spy(); // Verify that sinon.assert throws when spy hasn't been called t.throws( () => sinon.assert.calledOnce(spy), /expected spy to be called once but was called 0 times/i, "should throw when spy not called" ); // Call the spy spy(); // Now the assertion passes t.doesNotThrow( () => sinon.assert.calledOnce(spy), "should not throw when spy called once" ); t.end(); }); ``` ## Using a spy to wrap all object method `sinon.spy(object)` Spies all the object's methods. Note that it's usually better practice to spy individual methods, particularly on objects that you don't understand or control all the methods for (e.g. library dependencies). Spying individual methods tests intent more precisely and is less susceptible to unexpected behavior as the object's code evolves. The following is a slightly contrived example: ```js import t from "tap"; import sinon from "sinon"; t.test("spying on all object methods", (t) => { // External library example const myExternalLibrary = { getJSON(url) { return this._doNetworkCall({ url: url, dataType: "json" }); }, _doNetworkCall(httpParams) { return { result: 42 }; } }; const sandbox = sinon.createSandbox(); sandbox.spy(myExternalLibrary); // Call the method const url = "https://jsonplaceholder.typicode.com/todos/1"; myExternalLibrary.getJSON(url); // Verify both methods were spied on and called t.ok(myExternalLibrary.getJSON.calledOnce, "getJSON should be called once"); t.ok( myExternalLibrary._doNetworkCall.calledOnce, "_doNetworkCall should be called once" ); t.equal( myExternalLibrary._doNetworkCall.getCall(0).args[0].url, url, "url should match" ); t.equal( myExternalLibrary._doNetworkCall.getCall(0).args[0].dataType, "json", "dataType should be json" ); sandbox.restore(); t.end(); }); ``` ## Using a spy to wrap an existing method `sinon.spy(object, "method")` creates a spy that wraps the existing function `object.method`. The spy will behave exactly like the original method (including when used as a constructor), but you will have access to data about all [calls][call]. The following is a slightly contrived example: ```js import t from "tap"; import sinon from "sinon"; t.test("spying on an existing method", (t) => { // Mock a simple object with a method const myObject = { ajax(config) { // Simulate an ajax call return { data: "response" }; }, getJSON(url) { return this.ajax({ url: url, dataType: "json" }); } }; const sandbox = sinon.createSandbox(); sandbox.spy(myObject, "ajax"); // Call getJSON which internally uses ajax const url = "https://jsonplaceholder.typicode.com/todos/1"; myObject.getJSON(url); // Verify ajax was called correctly t.ok(myObject.ajax.calledOnce, "ajax should be called once"); t.equal(myObject.ajax.getCall(0).args[0].url, url, "url should match"); t.equal( myObject.ajax.getCall(0).args[0].dataType, "json", "dataType should be json" ); sandbox.restore(); t.end(); }); ``` ## Using a spy to wrap property getter and setter `sinon.spy(object, "property", ["get", "set"])` creates spies that wrap the getters and setters for `object.property`. The spies will behave exactly like the original getters and setters, but you will have access to data about all [calls][call]. Example: ```js import t from "tap"; import sinon from "sinon"; t.test("spying on property getter and setter", (t) => { const object = { _property: undefined, get test() { return this._property; }, set test(value) { this._property = value * 2; } }; const spy = sinon.spy(object, "test", ["get", "set"]); // Set the property object.test = 42; // Verify setter was called t.ok(spy.set.calledOnce, "setter should be called once"); // Get the property const result = object.test; // Verify getter was called and value is correct t.equal(result, 84, "value should be doubled"); t.ok(spy.get.calledOnce, "getter should be called once"); spy.get.restore(); spy.set.restore(); t.end(); }); ``` ## Creating spies: `sinon.spy()` Method Signatures ## Spy API Check out the [full list of methods and properties](/concepts/spies/api/). Spies provide a rich interface to inspect their usage. The above examples showed the `calledOnce` boolean property as well as the `getCall` method and the returned object's `args` property. There are three ways of inspecting [call][call] data. The preferred approach is to use the spy's `calledWith` method (and friends) because it keeps your test from being too specific about which call did what and so on. It will return `true` if the spy was ever called with the provided arguments. If you want to be specific, you can directly check the first argument of the first [call][call]. There are two ways of achieving this: The first example uses the two-dimensional `args` array directly on the spy, while the second example fetches the first [call][call] object and then accesses its `args` array. Which one to use is a matter of preference, but the recommended approach is going with `spy.calledWith(arg1, arg2, ...)` unless there's a need to make the tests highly specific. [call]: /concepts/spy-call/ [fakes]: /concepts/fakes/ --- --- url: /concepts/spies/api/always-called-on.md description: >- Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was always called with `object` as `this`. --- # `spy.alwaysCalledOn` Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was always called with `object` as `this`. `alwaysCalledOn` also accepts a matcher `spyCall.alwaysCalledOn(sinon.match(fn))` (see [matchers](/concepts/matchers/)). ```js import t from "tap"; import sinon from "sinon"; t.test("spy.alwaysCalledOn checks if always called with this", (t) => { const spy = sinon.spy(); const object = {}; const aDifferentObject = {}; // False before any calls t.notOk(spy.alwaysCalledOn(object), "should be false before calls"); // True after first call with object as this spy.call(object); t.ok(spy.alwaysCalledOn(object), "should be true after one matching call"); // Still true after second call with same this spy.call(object); t.ok(spy.alwaysCalledOn(object), "should stay true with consistent this"); // False after call with different this spy.call(aDifferentObject); t.notOk( spy.alwaysCalledOn(object), "should be false after inconsistent call" ); t.end(); }); ``` See [`Function.prototype.call()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call). ## Resetting `alwaysCalledOn` to default You can reset `alwaysCalledOn` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/always-called-with.md description: >- Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was always called with the provided arguments. --- # `spy.alwaysCalledWith` Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was always called with the provided arguments. Can be used for partial matching, Sinon only checks the provided arguments against actual arguments, so a call that received the provided arguments (in the same spots) and possibly others as well will return true. ```js import t from "tap"; import sinon from "sinon"; t.test("spy.alwaysCalledWith checks if always called with args", (t) => { const spy = sinon.spy(); // False before any calls t.notOk(spy.alwaysCalledWith("apple pie"), "should be false before calls"); // True after first call with that arg spy("apple pie"); t.ok( spy.alwaysCalledWith("apple pie"), "should be true after one matching call" ); // Still true after second call with same arg spy("apple pie"); t.ok( spy.alwaysCalledWith("apple pie"), "should stay true with consistent args" ); // False for different arg t.notOk( spy.alwaysCalledWith("lemon meringue pie"), "should be false for unused arg" ); // False after call with different arg spy("blueberry pie"); t.notOk( spy.alwaysCalledWith("apple pie"), "should be false after inconsistent call" ); // Reset sinon.resetHistory(); t.notOk(spy.alwaysCalledWith("apple pie"), "should be false after reset"); t.end(); }); ``` ## Resetting `alwaysCalledWith` to default You can reset `alwaysCalledWith` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/always-called-with-exactly.md description: >- Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was always called with the exact provided arguments. --- # `spy.alwaysCalledWithExactly` Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was always called with the exact provided arguments. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("spy.alwaysCalledWithExactly", (t) => { const spy = sinon.spy(); t.notOk( spy.alwaysCalledWithExactly("apple pie"), "returns false when spy has not been called" ); spy("apple pie"); t.ok( spy.alwaysCalledWithExactly("apple pie"), "returns true after first call with exact arguments" ); spy("apple pie"); t.ok( spy.alwaysCalledWithExactly("apple pie"), "returns true after second call with same exact arguments" ); spy("raspberry pie"); t.notOk( spy.alwaysCalledWithExactly("apple pie"), "returns false after a call with different arguments" ); t.end(); }); ``` ## Resetting `alwaysCalledWithExactly` to default You can reset `alwaysCalledWithExactly` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/always-called-with-match.md description: >- Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was always called with matching arguments (and possibly others). --- # `spy.alwaysCalledWithMatch` Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was always called with matching arguments (and possibly others). This behaves the same as `spy.alwaysCalledWith(sinon.match(arg1), sinon.match(arg2), ...)`. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("spy.alwaysCalledWithMatch", (t) => { const spy = sinon.spy(); const object = { a: 1, b: 2, c: 3 }; spy(object); t.ok( spy.alwaysCalledWithMatch({ b: 2 }), "returns true after first call with matching partial object" ); spy(object); t.ok( spy.alwaysCalledWithMatch({ b: 2 }), "returns true after second call with matching partial object" ); spy("apple pie"); t.notOk( spy.alwaysCalledWithMatch({ b: 2 }), "returns false after a call with non-matching arguments" ); t.end(); }); ``` ## Resetting `alwaysCalledWithMatch` to default You can reset `alwaysCalledWithMatch` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/always-returned.md description: >- Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) always returned the provided value. --- # `spy.alwaysReturned` Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) always returned the provided value. Uses deep comparison for objects and arrays. Use `spy.alwaysReturned(sinon.match.same(obj))` for strict comparison (see [matchers](/concepts/matchers/)). ```js import t from "tap"; import sinon from "sinon"; t.test("spy.alwaysReturned checks if spy always returned value", (t) => { const f = sinon.fake.returns("apple pie"); // Call twice f(); f(); // Verify alwaysReturned checks t.ok(f.alwaysReturned("apple pie"), "should return true for 'apple pie'"); t.notOk( f.alwaysReturned("raspberry pie"), "should return false for 'raspberry pie'" ); // Reset and verify sinon.reset(); t.notOk(f.alwaysReturned("apple pie"), "should return false after reset"); t.end(); }); ``` ## Resetting `alwaysReturned` to default You can reset `alwaysReturned` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/always-threw.md description: >- Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) always threw an exception. --- # `spy.alwaysThrew` Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) always threw an exception. ```js import t from "tap"; import sinon from "sinon"; t.test("spy.alwaysThrew checks if spy always threw", (t) => { const f = sinon.fake.throws(new Error("oh dear")); // Call twice (catching exceptions) try { f(); } catch (e) { // Expected } try { f(); } catch (e) { // Expected } // Verify alwaysThrew t.ok(f.alwaysThrew(), "should return true when always threw"); // Reset sinon.reset(); t.notOk(f.alwaysThrew(), "should return false after reset"); t.end(); }); t.test("spy.alwaysThrew checks for specific exception type", (t) => { const f = sinon.fake.throws(new TypeError("a specific error")); // Call twice (catching exceptions) try { f(); } catch (e) { // Expected } try { f(); } catch (e) { // Expected } // Verify alwaysThrew with type t.ok(f.alwaysThrew("TypeError"), "should return true for matching type"); t.notOk( f.alwaysThrew("ArgumentError"), "should return false for non-matching type" ); // Reset sinon.reset(); t.notOk(f.alwaysThrew("TypeError"), "should return false after reset"); t.end(); }); ``` ## Resetting `alwaysThrew` to default You can reset `alwaysThrew` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/called-after.md description: >- Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was called after another, otherwise returns `false`. --- # `spy.calledAfter` Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was called after another, otherwise returns `false`. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("spy.calledAfter", (t) => { const fake = sinon.fake(); const spy = sinon.spy(); const stub = sinon.stub(); t.notOk(fake.calledAfter(spy), "returns false before any calls"); fake(); spy(); stub(); t.notOk(fake.calledAfter(spy), "fake was not called after spy"); t.notOk(spy.calledAfter(stub), "spy was not called after stub"); t.ok(stub.calledAfter(fake), "stub was called after fake"); t.end(); }); ``` ## Resetting `calledAfter` to default You can reset `calledAfter` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/called-before.md description: >- Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was called before another, otherwise returns `false`. --- # `spy.calledBefore` Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was called before another, otherwise returns `false`. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("spy.calledBefore", (t) => { const fake = sinon.fake(); const spy = sinon.spy(); const stub = sinon.stub(); t.notOk(fake.calledBefore(spy), "returns false before any calls"); fake(); spy(); stub(); t.ok(fake.calledBefore(spy), "fake was called before spy"); t.ok(spy.calledBefore(stub), "spy was called before stub"); t.notOk(stub.calledBefore(fake), "stub was not called before fake"); t.end(); }); ``` ## Resetting `calledBefore` to default You can reset `calledBefore` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/called-immediately-after.md description: >- Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was called after another, and no [`calls`](/concepts/spy-call/) occurred between them. --- # `spy.calledImmediatelyAfter` Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was called after another, and no [`calls`](/concepts/spy-call/) occurred between them. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("spy.calledImmediatelyAfter", (t) => { const fake = sinon.fake(); const spy = sinon.spy(); const stub = sinon.stub(); t.notOk(fake.calledImmediatelyAfter(spy), "returns false before any calls"); fake(); spy(); stub(); t.notOk( fake.calledImmediatelyAfter(spy), "fake was not called immediately after spy" ); t.ok( spy.calledImmediatelyAfter(fake), "spy was called immediately after fake" ); t.notOk( stub.calledImmediatelyAfter(fake), "stub was not called immediately after fake" ); t.end(); }); ``` ## Resetting `calledImmediatelyAfter` to default You can reset `calledImmediatelyAfter` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/called-immediately-before.md description: >- Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was called before another, and no [`calls`](/concepts/spy-call/) occurred between them. --- # `spy.calledImmediatelyBefore` Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was called before another, and no [`calls`](/concepts/spy-call/) occurred between them. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("spy.calledImmediatelyBefore", (t) => { const fake = sinon.fake(); const spy = sinon.spy(); const stub = sinon.stub(); t.notOk(fake.calledImmediatelyBefore(spy), "returns false before any calls"); fake(); spy(); stub(); t.ok( fake.calledImmediatelyBefore(spy), "fake was called immediately before spy" ); t.ok( spy.calledImmediatelyBefore(stub), "spy was called immediately before stub" ); t.notOk( stub.calledImmediatelyBefore(fake), "stub was not called immediately before fake" ); t.end(); }); ``` ## Resetting `calledImmediatelyBefore` to default You can reset `calledImmediatelyBefore` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/called-on.md description: >- Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was called at least once with `object` as `this`. --- # `spy.calledOn` Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was called at least once with `object` as `this`. `calledOn` also accepts a matcher `spyCall.calledOn(sinon.match(fn))` (see [matchers](/concepts/matchers/)). ```js import t from "tap"; import sinon from "sinon"; t.test("spy.calledOn checks if called at least once with this", (t) => { const spy = sinon.spy(); const object = {}; // False before any calls t.notOk(spy.calledOn(object), "should be false before calls"); // True after calling with object as this spy.call(object); t.ok( spy.calledOn(object), "should be true after calling with object as this" ); t.end(); }); ``` See [`Function.prototype.call()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call). ## Resetting `calledOn` to default You can reset `calledOn` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/called-once-with-exactly.md description: >- Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was called exactly once in total and that one call was using the exact provided arguments and no others. --- # `spy.calledOnceWithExactly` Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was called exactly once in total and that one call was using the exact provided arguments and no others. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("spy.calledOnceWithExactly", (t) => { const spy = sinon.spy(); t.notOk( spy.calledOnceWithExactly("apple pie"), "returns false when spy has not been called" ); spy("apple pie"); t.ok( spy.calledOnceWithExactly("apple pie"), "returns true when spy was called exactly once with exact arguments" ); spy("apple pie"); t.notOk( spy.calledOnceWithExactly("apple pie"), "returns false when spy was called more than once" ); // reset the history of everything sinon.resetHistory(); spy("apple pie", "blueberry pie"); t.notOk( spy.calledOnceWithExactly("apple pie"), "returns false when arguments don't match exactly" ); t.end(); }); ``` ## Resetting `calledOnceWithExactly` to default You can reset `calledOnceWithExactly` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/called-with.md description: >- Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was called at least once with the provided arguments. --- # `spy.calledWith` Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was called at least once with the provided arguments. Can be used for partial matching, Sinon only checks the provided arguments against actual arguments, so a call that received the provided arguments (in the same spots) and possibly others as well will return true. ```js import t from "tap"; import sinon from "sinon"; t.test("spy.calledWith checks if spy was called with arguments", (t) => { const spy = sinon.spy(); // False before any calls t.equal( spy.calledWith("apple pie"), false, "should be false before any calls" ); // True after calling with the argument spy("apple pie"); t.equal( spy.calledWith("apple pie"), true, "should be true after calling with 'apple pie'" ); // False for arguments that were never used t.equal( spy.calledWith("lemon meringue pie"), false, "should be false for unused arguments" ); // Reset history sinon.resetHistory(); // False after reset t.equal( spy.calledWith("apple pie"), false, "should be false after resetHistory" ); t.end(); }); ``` ## Resetting `calledWith` to default You can reset `calledWith` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/called-with-exactly.md description: >- Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was called at least once with the provided arguments and no others. --- # `spy.calledWithExactly` Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was called at least once with the provided arguments and no others. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("spy.calledWithExactly", (t) => { const spy = sinon.spy(); t.notOk( spy.calledWithExactly("apple pie"), "returns false when spy has not been called" ); spy("apple pie"); t.ok( spy.calledWithExactly("apple pie"), "returns true when called with exact arguments" ); // reset the history of everything sinon.resetHistory(); spy("apple pie", "blueberry pie"); spy("apple pie", "blueberry pie"); spy("apple pie", "blueberry pie"); t.ok( spy.calledWithExactly("apple pie", "blueberry pie"), "returns true when called with exact multiple arguments" ); // reset the history of everything sinon.resetHistory(); t.notOk( spy.calledWithExactly("apple pie", "blueberry pie"), "returns false after reset with no calls" ); // reset the history of everything sinon.resetHistory(); spy("apple pie"); spy("blueberry pie"); t.ok( spy.calledWithExactly("apple pie"), "returns true when spy was called with exact arguments (at least once)" ); t.end(); }); ``` ## Resetting `calledWithExactly` to default You can reset `calledWithExactly` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/called-with-match.md description: >- Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was called with matching arguments (and possibly others). --- # `spy.calledWithMatch` Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was called with matching arguments (and possibly others). This behaves the same as `spy.calledWith(sinon.match(arg1), sinon.match(arg2), ...)`. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("spy.calledWithMatch", (t) => { const spy = sinon.spy(); const object = { a: 1, b: 2, c: 3 }; spy(object); t.ok( spy.calledWithMatch({ b: 2 }), "returns true when spy was called with matching partial object" ); t.notOk( spy.calledWithMatch({ b: 1 }), "returns false when spy was not called with matching arguments" ); t.end(); }); ``` ## Resetting `calledWithMatch` to default You can reset `calledWithMatch` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/called-with-new.md description: >- Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was called the `new` operator. --- # `spy.calledWithNew` Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was called the `new` operator. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("spy.calledWithNew", (t) => { const spy = sinon.spy(); t.notOk( spy.calledWithNew("apple pie"), "returns false when spy has not been called" ); new spy("apple pie"); t.ok( spy.calledWithNew("apple pie"), "returns true when spy was called with new operator" ); t.end(); }); ``` ## Resetting `calledWithNew` to default You can reset `calledWithNew` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/called-once-with.md description: >- Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was called exactly once and that one call was made using the provided arguments. --- # `spy.calledOnceWith` Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was called exactly once and that one call was made using the provided arguments. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("spy.calledOnceWith", (t) => { const spy = sinon.spy(); t.notOk( spy.calledOnceWith("apple pie", "coffee"), "returns false when spy has not been called" ); spy("apple pie", "coffee"); t.ok( spy.calledOnceWith("apple pie", "coffee"), "returns true when spy was called exactly once with arguments" ); spy("apple pie", "coffee"); t.notOk( spy.calledOnceWith("apple pie", "coffee"), "returns false when spy was called more than once" ); t.end(); }); ``` ## Resetting `calledOnceWith` to default You can reset `calledOnceWith` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/get-call.md description: >- Returns the _nth_ (zero-indexed) [call](/concepts/spy-call/) recorded by the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/). --- # `spy.getCall` Returns the *nth* (zero-indexed) [call](/concepts/spy-call/) recorded by the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/). If *n* is negative, the *nth* call from the end is returned. For example, `spy.getCall(-1)` returns the last call, and `spy.getCall(-2)` returns the second to last call. Accessing individual calls helps with more detailed behavior verification when the spy is called more than once. ```js import t from "tap"; import sinon from "sinon"; t.test("spy.getCall returns the nth call", (t) => { const f = sinon.fake(); f("a"); f("b"); f("c"); // Get second call (index 1) const call = f.getCall(1); t.ok(call, "should return a call object"); t.same(call.args, ["b"], "should have args from second call"); t.equal(call.firstArg, "b", "firstArg should be 'b'"); // Reset and verify null sinon.reset(); t.equal(f.getCall(1), null, "should return null after reset"); t.end(); }); ``` ## Resetting `getCall` to default You can reset the call history in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/get-calls.md description: >- Returns an `Array` of all [calls](/concepts/spy-call/) recorded by the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/). --- # `spy.getCalls` Returns an `Array` of all [calls](/concepts/spy-call/) recorded by the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/). ```js import t from "tap"; import sinon from "sinon"; t.test("spy.getCalls returns array of all call objects", (t) => { const f = sinon.fake(); f("a"); f("b"); const calls = f.getCalls(); // Verify we have two call objects t.equal(calls.length, 2, "should have 2 calls"); // Verify first call t.ok(calls[0], "first call should exist"); t.same(calls[0].args, ["a"], "first call should have args ['a']"); t.equal(calls[0].firstArg, "a", "first call firstArg should be 'a'"); // Verify second call t.ok(calls[1], "second call should exist"); t.same(calls[1].args, ["b"], "second call should have args ['b']"); t.equal(calls[1].firstArg, "b", "second call firstArg should be 'b'"); t.end(); }); ``` ## Resetting `getCalls` to default You can reset the call history in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/never-called-with.md description: >- Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was never called with the provided arguments. --- # `spy.neverCalledWith` Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was never called with the provided arguments. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("spy.neverCalledWith", (t) => { const spy = sinon.spy(); t.ok( spy.neverCalledWith("apple pie"), "returns true when spy has not been called at all" ); spy("apple pie"); t.notOk( spy.neverCalledWith("apple pie"), "returns false when spy was called with those arguments" ); t.ok( spy.neverCalledWith("blueberry pie"), "returns true when spy was not called with specific arguments" ); spy("blueberry pie"); t.notOk( spy.neverCalledWith("blueberry pie"), "returns false when spy was now called with those arguments" ); // reset the history of everything sinon.resetHistory(); t.ok(spy.neverCalledWith("apple pie"), "returns true after reset"); t.end(); }); ``` ## Resetting `neverCalledWith` to default You can reset `neverCalledWith` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/never-called-with-match.md description: >- Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was never called with matching arguments. --- # `spy.neverCalledWithMatch` Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was never called with matching arguments. This behaves the same as `spy.neverCalledWith(sinon.match(arg1), sinon.match(arg2), ...)`. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("spy.neverCalledWithMatch", (t) => { const spy = sinon.spy(); const object = { a: 1, b: 2, c: 3 }; t.ok( spy.neverCalledWithMatch({ b: 2 }), "returns true when spy has not been called" ); spy(object); t.notOk( spy.neverCalledWithMatch({ b: 2 }), "returns false when spy was called with matching partial object" ); spy("apple pie"); t.ok( spy.neverCalledWithMatch("blueberry pie"), "returns true when spy was not called with matching arguments" ); t.end(); }); ``` ## Resetting `neverCalledWithMatch` to default You can reset `neverCalledWithMatch` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) ## weight: 100 # `spy.neverCalledWithMatch` Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was always called with matching arguments (and possibly others). This behaves the same as `spy.alwaysCalledWith(sinon.match(arg1), sinon.match(arg2), ...)`. ```js import * as sinon from "sinon"; const spy = sinon.spy(); const object = { a: 1, b: 2, c: 3 }; spy(object); spy.neverCalledWithMatch({ b: 2 }); // => true spy(object); spy.neverCalledWithMatch({ b: 2 }); // => true spy("apple pie"); spy.neverCalledWithMatch({ b: 2 }); // => false ``` ## Resetting `neverCalledWithMatch` to default You can reset `neverCalledWithMatch` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/printf.md description: '`spy.printf("format string", [arg1, arg2, ...]);`' --- # `spy.printf` `spy.printf("format string", [arg1, arg2, ...]);` Returns the passed format string with the following replacements performed: ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("spy.printf", (t) => { const s = sinon.spy(function hello() { return "world"; }); s(); s(); t.equal(s.printf("%n"), "hello", "formats spy name with %n"); t.equal( s.printf("The spy %n has been called %c"), "The spy hello has been called twice", "formats spy name and call count" ); t.end(); }); ``` --- --- url: /concepts/spies/api/reset-history.md description: >- Resets the state of a [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/). --- # `spy.resetHistory` Resets the state of a [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/). ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("spy.resetHistory", (t) => { const s = sinon.spy(); t.equal(s.callCount, 0, "callCount starts at 0"); s(); t.equal(s.callCount, 1, "callCount is 1 after one call"); s.resetHistory(); t.equal(s.callCount, 0, "callCount is 0 after resetHistory"); s(); t.equal(s.callCount, 1, "callCount is 1 again after another call"); t.end(); }); ``` In the example above, we can see that `callCount` is reset. Resetting history resets **all** recording properties of a [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/), there are too many to list here. ## Other ways of resetting history You can also reset the history for the whole sandbox, by using [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) or [`sinon.reset`](/concepts/sandboxes/api/reset). --- --- url: /concepts/spies/api/restore.md description: >- Replaces the [`spy`](../) or [`stub`](/concepts/stubs/) with the original method. Only available if the [`spy`](../) or [`stub`](/concepts/stubs/) replaced an existing method. --- # `spy.restore` Replaces the [`spy`](../) or [`stub`](/concepts/stubs/) with the original method. Only available if the [`spy`](../) or [`stub`](/concepts/stubs/) replaced an existing method. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("spy.restore", (t) => { const obj = { hello: () => { return "world"; } }; const s = sinon.stub(obj, "hello").callsFake(() => { return "sailor"; }); t.equal(obj.hello(), "sailor", "stub returns stubbed value"); s.restore(); t.equal(obj.hello(), "world", "original method restored"); t.end(); }); ``` ## Other ways of restoring You can also reset the whole sandbox, by using [`sinon.restore`](/concepts/sandboxes/api/restore) or [`sinon.reset`](/concepts/sandboxes/api/reset). --- --- url: /concepts/spies/api/returned.md description: >- Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) returned the provided value at least once. --- # `spy.returned` Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) returned the provided value at least once. Uses deep comparison for objects and arrays. Use `spy.returned(sinon.match.same(obj))` for strict comparison (see [matchers](/concepts/matchers/)). ```js import t from "tap"; import sinon from "sinon"; t.test("spy.returned checks if spy returned a value", (t) => { const f = sinon.fake.returns("apple pie"); // Call the fake const result = f(); t.equal(result, "apple pie", "should return 'apple pie'"); // Verify returned checks t.ok(f.returned("apple pie"), "should return true for 'apple pie'"); t.notOk( f.returned("raspberry pie"), "should return false for 'raspberry pie'" ); // Reset and verify sinon.reset(); t.notOk(f.returned("apple pie"), "should return false after reset"); t.end(); }); ``` ## Resetting `returned` to default You can reset `returned` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/threw.md description: >- Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) threw an exception at least once. --- # `spy.threw` Returns `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) threw an exception at least once. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("spy.threw - general", (t) => { const f = sinon.fake.throws(new Error("oh dear")); try { f(); } catch (e) { // Expected to throw } t.ok(f.threw(), "returns true when fake threw an exception"); sinon.reset(); t.notOk(f.threw(), "returns false after reset"); t.end(); }); ``` ## `spy.threw("TypeError")` Returns `true` , when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) threw an exception of the provided type at least once. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("spy.threw(type) - specific type", (t) => { const f = sinon.fake.throws(new TypeError("a specific error")); try { f(); } catch (e) { // Expected to throw } t.ok(f.threw("TypeError"), "returns true when fake threw TypeError"); t.notOk( f.threw("ArgumentError"), "returns false when fake did not throw ArgumentError" ); sinon.reset(); t.notOk(f.threw("TypeError"), "returns false after reset"); t.end(); }); ``` ## Resetting `threw` to default You can reset `threw` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/with-args.md description: >- Creates a [`spy`](../) or [`stub`](/concepts/stubs/) that only records [calls](/concepts/spy-call/) when the received arguments match those passed to `withArgs`. This is useful to be more expressive in your ... --- # `spy.withArgs` Creates a [`spy`](../) or [`stub`](/concepts/stubs/) that only records [calls](/concepts/spy-call/) when the received arguments match those passed to `withArgs`. This is useful to be more expressive in your assertions, where you can access the spy with the same [call](/concepts/spy-call/). Uses deep comparison for objects and arrays. Use `spy.withArgs(sinon.match.same(obj))` for strict comparison (see [matchers](/concepts/matchers/)). ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("spy.withArgs", (t) => { const object = { method() {} }; const spy = sinon.spy(object, "method"); object.method(42); object.method(1); t.ok(spy.withArgs(42).calledOnce, "spy.withArgs(42) was called once"); t.ok(spy.withArgs(1).calledOnce, "spy.withArgs(1) was called once"); object.method("a", "b", "c"); t.ok( spy.withArgs("a", "b", "c").calledOnce, "spy.withArgs with multiple arguments was called once" ); spy.restore(); t.end(); }); ``` --- --- url: /concepts/spies/api/args.md description: >- Array of arguments received, `spy.args[0]` is an array of arguments received in the first [call](/concepts/spy-call/) of the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/). --- # `spy.args` Array of arguments received, `spy.args[0]` is an array of arguments received in the first [call](/concepts/spy-call/) of the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/). ```js import t from "tap"; import sinon from "sinon"; t.test("spy.args contains all arguments from all calls", (t) => { const f = sinon.fake(); // Call with different arguments f("a", "b", "c"); f("d", "e", "f"); // Verify args structure t.same( f.args, [ ["a", "b", "c"], ["d", "e", "f"] ], "args should contain all calls" ); t.same(f.args[0], ["a", "b", "c"], "args[0] should be first call arguments"); t.end(); }); ``` ## Resetting `args` to default You can reset `args` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/call-count.md description: >- The number of recorded [calls](/concepts/spy-call/) recorded by the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/). --- # `spy.callCount` The number of recorded [calls](/concepts/spy-call/) recorded by the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/). ```js import t from "tap"; import sinon from "sinon"; t.test("spy.callCount tracks number of calls", (t) => { const f = sinon.fake(); // Initially 0 t.equal(f.callCount, 0, "should be 0 before any calls"); // Increments with each call f(); t.equal(f.callCount, 1, "should be 1 after first call"); f(); t.equal(f.callCount, 2, "should be 2 after second call"); f(); t.equal(f.callCount, 3, "should be 3 after third call"); t.end(); }); ``` ## Resetting `callCount` to default You can reset `callCount` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/called.md description: >- `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was called one or more times. --- # `spy.called` `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) was called one or more times. ```js import t from "tap"; import sinon from "sinon"; t.test("spy.called is false initially, true after any call", (t) => { const f = sinon.fake(); // Initially false t.equal(f.called, false, "should be false before any calls"); // True after first call f(); t.equal(f.called, true, "should be true after first call"); // Still true after second call f(); t.equal(f.called, true, "should remain true after multiple calls"); t.end(); }); ``` ## Resetting `called` to default You can reset `called` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/called-once.md description: >- `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) has been called exactly once. --- # `spy.calledOnce` `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) has been called exactly once. ```js import t from "tap"; import sinon from "sinon"; t.test("spy.calledOnce is true only when called exactly once", (t) => { const f = sinon.fake(); // Initially false t.equal(f.calledOnce, false, "should be false before any calls"); // True after first call f(); t.equal(f.calledOnce, true, "should be true after one call"); // False after second call f(); t.equal(f.calledOnce, false, "should be false after two calls"); t.end(); }); ``` ## Resetting `calledOnce` to default You can reset `calledOnce` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/called-thrice.md description: >- `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) has been called exactly thrice. --- # `spy.calledThrice` `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) has been called exactly thrice. ```js import t from "tap"; import sinon from "sinon"; t.test("spy.calledThrice is true only when called exactly three times", (t) => { const f = sinon.fake(); // Initially false t.equal(f.calledThrice, false, "should be false before any calls"); // False after first call f(); t.equal(f.calledThrice, false, "should be false after one call"); // False after second call f(); t.equal(f.calledThrice, false, "should be false after two calls"); // True after third call f(); t.equal(f.calledThrice, true, "should be true after three calls"); // False after fourth call f(); t.equal(f.calledThrice, false, "should be false after four calls"); t.end(); }); ``` ## Resetting `calledThrice` to default You can reset `calledThrice` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/called-twice.md description: 'True when the fake, spy, or stub has been called exactly twice.' --- # `spy.calledTwice` `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) has been called exactly twice. ```js import t from "tap"; import sinon from "sinon"; t.test("spy.calledTwice is true only when called exactly twice", (t) => { const f = sinon.fake(); // Initially false t.equal(f.calledTwice, false, "should be false before any calls"); // False after first call f(); t.equal(f.calledTwice, false, "should be false after one call"); // True after second call f(); t.equal(f.calledTwice, true, "should be true after two calls"); // False after third call f(); t.equal(f.calledTwice, false, "should be false after three calls"); t.end(); }); ``` ## Resetting `calledTwice` to default You can reset `calledTwice` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/exceptions.md description: >- Array of exception objects thrown, `spy.exceptions[0]` is the exception thrown by the first [call](/concepts/spy-call/) of the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/). --- # `spy.exceptions` Array of exception objects thrown, `spy.exceptions[0]` is the exception thrown by the first [call](/concepts/spy-call/) of the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/). If the call did not throw an error, the value at the call's location in `.exceptions` will be `undefined`. ```js import t from "tap"; import sinon from "sinon"; t.test("spy.exceptions contains all thrown exceptions", (t) => { const error = new TypeError("apple pie"); const f = sinon.fake.throws(error); // Call twice (catching exceptions) try { f(); } catch (e) { // Expected } try { f(); } catch (e) { // Expected } // Verify exceptions array t.equal(f.exceptions.length, 2, "should have 2 exceptions"); t.equal(f.exceptions[0], error, "first exception should be the error"); t.equal(f.exceptions[1], error, "second exception should be the error"); t.equal( f.exceptions[0].message, "apple pie", "exception message should match" ); t.end(); }); ``` ## Resetting `exceptions` to default You can reset `exceptions` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/first-call.md description: >- The first [`call`](/concepts/spy-call/) object of the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/). --- # `spy.firstCall` The first [`call`](/concepts/spy-call/) object of the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/). ```js import t from "tap"; import sinon from "sinon"; t.test("spy.firstCall returns the first call object", (t) => { const f = sinon.fake(); // Initially null t.equal(f.firstCall, null, "should be null before any calls"); // Returns first call object f("apple pie"); t.ok(f.firstCall, "should have firstCall after first call"); t.same(f.firstCall.args, ["apple pie"], "firstCall should have correct args"); t.equal(f.firstCall.firstArg, "apple pie", "firstArg should be 'apple pie'"); // Still returns first call even after second call f("blueberry pie"); t.same( f.firstCall.args, ["apple pie"], "firstCall should still be first call" ); t.equal( f.firstCall.firstArg, "apple pie", "firstArg should still be 'apple pie'" ); t.end(); }); ``` ## Resetting `firstCall` to default You can reset `firstCall` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/last-call.md description: >- The last [`call`](/concepts/spy-call/) object of the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/). --- # `spy.lastCall` The last [`call`](/concepts/spy-call/) object of the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/). ```js import t from "tap"; import sinon from "sinon"; t.test("spy.lastCall returns the last call object", (t) => { const f = sinon.fake(); // Initially null t.equal(f.lastCall, null, "should be null before any calls"); // Returns first (and only) call object f("apple pie"); t.ok(f.lastCall, "should have lastCall after first call"); t.same(f.lastCall.args, ["apple pie"], "lastCall should have correct args"); t.equal(f.lastCall.firstArg, "apple pie", "firstArg should be 'apple pie'"); // Returns second (now last) call after second call f("blueberry pie"); t.same( f.lastCall.args, ["blueberry pie"], "lastCall should be most recent call" ); t.equal( f.lastCall.firstArg, "blueberry pie", "firstArg should be 'blueberry pie'" ); t.end(); }); ``` ## Resetting `lastCall` to default You can reset `lastCall` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/not-called.md description: >- `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) has not been called. --- # `spy.notCalled` `true`, when the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/) has not been called. ```js import t from "tap"; import sinon from "sinon"; t.test("spy.notCalled is true only when never called", (t) => { const f = sinon.fake(); // Initially true t.equal(f.notCalled, true, "should be true before any calls"); // False after first call f(); t.equal(f.notCalled, false, "should be false after first call"); // Still false after second call f(); t.equal(f.notCalled, false, "should remain false after multiple calls"); t.end(); }); ``` ## Resetting `notCalled` to default You can reset `notCalled` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/return-values.md description: >- Array of return values, `spy.returnValues[0]` is the return value of the first [call](/concepts/spy-call/) of the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/). --- # `spy.returnValues` Array of return values, `spy.returnValues[0]` is the return value of the first [call](/concepts/spy-call/) of the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/). If the call did not explicitly return a value, the value at the call's location in `.returnValues` will be `undefined`. ```js import t from "tap"; import sinon from "sinon"; t.test("spy.returnValues contains all return values", (t) => { const f = sinon.fake.returns("apple pie"); // Call twice const result1 = f(); t.equal(result1, "apple pie", "first call should return 'apple pie'"); const result2 = f(); t.equal(result2, "apple pie", "second call should return 'apple pie'"); // Verify returnValues array t.same( f.returnValues, ["apple pie", "apple pie"], "returnValues should contain both returns" ); t.equal( f.returnValues[0], "apple pie", "returnValues[0] should be 'apple pie'" ); t.end(); }); ``` ## Resetting `returnValues` to default You can reset `returnValues` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/second-call.md description: >- The second [`call`](/concepts/spy-call/) object of the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/). --- # `spy.secondCall` The second [`call`](/concepts/spy-call/) object of the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/). ```js import t from "tap"; import sinon from "sinon"; t.test("spy.secondCall returns the second call object", (t) => { const f = sinon.fake(); // Initially null t.equal(f.secondCall, null, "should be null before any calls"); // Still null after first call f("apple pie"); t.equal(f.secondCall, null, "should be null after only one call"); // Returns second call object after second call f("blueberry pie"); t.ok(f.secondCall, "should have secondCall after second call"); t.same( f.secondCall.args, ["blueberry pie"], "secondCall should have correct args" ); t.equal( f.secondCall.firstArg, "blueberry pie", "firstArg should be 'blueberry pie'" ); t.end(); }); ``` ## Resetting `secondCall` to default You can reset `secondCall` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/third-call.md description: >- The third [`call`](/concepts/spy-call/) object of the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/). --- # `spy.thirdCall` The third [`call`](/concepts/spy-call/) object of the [`fake`](/concepts/fakes/), [`spy`](../) or [`stub`](/concepts/stubs/). ```js import t from "tap"; import sinon from "sinon"; t.test("spy.thirdCall returns the third call object", (t) => { const f = sinon.fake(); // Initially null t.equal(f.thirdCall, null, "should be null before any calls"); // Still null after first call f("apple pie"); t.equal(f.thirdCall, null, "should be null after one call"); // Still null after second call f("blueberry pie"); t.equal(f.thirdCall, null, "should be null after two calls"); // Returns third call object after third call f("cherry pie"); t.ok(f.thirdCall, "should have thirdCall after third call"); t.same( f.thirdCall.args, ["cherry pie"], "thirdCall should have correct args" ); t.equal( f.thirdCall.firstArg, "cherry pie", "firstArg should be 'cherry pie'" ); t.end(); }); ``` ## Resetting `thirdCall` to default You can reset `thirdCall` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/spies/api/this-values.md description: >- Array of `this` objects, `spy.thisValues[0]` is the [`this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) object for the first [call](/concepts/spy-call/). --- # `spy.thisValues` Array of `this` objects, `spy.thisValues[0]` is the [`this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) object for the first [call](/concepts/spy-call/). ```js import t from "tap"; import sinon from "sinon"; t.test("spy.thisValues contains this objects from all calls", (t) => { const spy = sinon.spy(); const object = { apple: "pie" }; // Call with specific this value spy.call(object, "hello"); // Verify thisValues array t.same(spy.thisValues, [object], "thisValues should contain the object"); t.equal( spy.thisValues[0], object, "thisValues[0] should be the exact same object" ); t.end(); }); ``` See [`Function.prototype.call()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call). ## Resetting `thisValues` to default You can reset `thisValues` in three different ways: * [`spy.resetHistory`](reset-history) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/stubs.md description: >- Functions with pre-programmed behavior. Like spies but with methods to configure return values, exceptions, and callbacks. --- # Stubs ## What are stubs? ::: warning Consider Using Fakes Instead [Fakes][fakes] are the recommended alternative to stubs for most use cases. They provide simpler, immutable behavior with the same spy API. Consider using `sinon.fake.returns()`, `sinon.fake.throws()`, etc. instead of stubs for new code. Stubs remain useful for advanced scenarios like call-specific behavior (`onCall()`) and property stubbing. ::: Test stubs are functions, like [spies][spies], with pre-programmed behavior. They support the full [test spy API][spy-api] in addition to methods which can be used to alter the stub's behavior. Like spies, stubs can be either standalone, or wrap existing functions. When wrapping an existing function with a stub, the original function is not called. ## When to use stubs? Use a stub when you want to: 1. Control a method's behavior from a test, to force the code down a specific path. Examples include forcing a method to throw an error in order to test error handling. ```js import * as sinon from "sinon"; const o = { greet: function (name) { console.log(`Hello ${name}`); } }; // stub out the greet method and make it throw the error we need for our test const stub = sinon.stub(o, "greet").throws(new Error("I lost my pie :(")); try { o.greet("Eleanor Rigby"); } catch (error) { console.log(error); // => I lost my pie :( } ``` 2. When you want to prevent a specific method from being called directly (possibly because it triggers undesired behavior, such as `readFile` or similar). ```js import * as sinon from "sinon"; import * as fs from "fs"; // stub out the readFile method sinon.stub(fs, "readFile").callsFake(function () { // and make it return the value we want for our test return Promise.resolve("Apple pie"); }); const fileContent = await fs.readFile("somefile"); console.log(fileContent); // => Apple pie ``` ## Defining stub behavior on consecutive calls Calling behavior defining methods like [`returns`][returns] or [`throws`][throws] multiple times overrides the behavior of the stub. You can use the [`onCall`][on-call] method to make a stub respond differently on consecutive calls. [fakes]: /concepts/fakes/ [matchers]: /concepts/matchers/ [spies]: /concepts/spies/ [spy-api]: /concepts/spies/api/ [on-call]: ./api/on-call [returns]: ./api/returns [throws]: ./api/throws --- --- url: /concepts/stubs/api/add-behavior.md description: >- Add a custom behavior. The name will be available as a function on stubs, and the chaining mechanism will be set up for you (e.g. no need to return anything from your function, its return value wil... --- # `stub.addBehavior(name, fn)` Add a custom behavior. The name will be available as a function on stubs, and the chaining mechanism will be set up for you (e.g. no need to return anything from your function, its return value will be ignored). The `fn` will be passed the fake instance as its first argument, and then the user's arguments. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.addBehavior - custom behavior", (t) => { const name = "returnsNum"; function fn(fake, n) { fake.returns(n); } sinon.addBehavior("returnsNum", fn); const stub = sinon.stub().returnsNum(42); const result = stub(); t.equal(result, 42, "custom behavior returns 42"); t.end(); }); ``` --- --- url: /concepts/stubs/api/call-arg.md description: Invokes a callback passed to the `stub` at a given `index`. --- # `stub.callArg(index)` Invokes a callback passed to the `stub` at a given `index`. Useful when a function is called with more than one callback, and calling the first callback is undesirable. ```js import t from "tap"; import sinon from "sinon"; t.test("stub.callArg invokes callback at specified index", (t) => { const stub = sinon.stub(); const index = 1; const callback0 = sinon.fake.returns("Success!"); const callback1 = sinon.fake.returns("Oh noes!"); stub(callback0, callback1); const result = stub.callArg(index); // Verify the right callback was called t.notOk(callback0.called, "callback0 should not be called"); t.ok(callback1.calledOnce, "callback1 should be called once"); // Verify the return value t.same(result, ["Oh noes!"]); t.end(); }); ``` ## See also * [stub.callArgWith](./call-arg-with) --- --- url: /concepts/stubs/api/call-arg-with.md description: >- Invokes a callback passed to the `stub` at a given `index`, with given arguments. --- # `stub.callArgWith(index, ...)` Invokes a callback passed to the `stub` at a given `index`, with given arguments. Useful when a function is called with more than one callback, and calling the first callback is undesirable. ```js import t from "tap"; import sinon from "sinon"; t.test("stub.callArgWith invokes callback at index with arguments", (t) => { const stub = sinon.stub(); const index = 1; const callback0 = sinon.fake.returns("Hi Joe"); const callback1 = sinon.fake.returns("Hey Joe"); stub(callback0, callback1); const result = stub.callArgWith(index, "Joe"); // Verify the right callback was called with correct arguments t.notOk(callback0.called, "callback0 should not be called"); t.ok(callback1.calledOnce, "callback1 should be called once"); t.ok(callback1.calledWith("Joe"), "callback1 should be called with 'Joe'"); // Verify the return value t.same(result, ["Hey Joe"]); t.end(); }); ``` ## See also * [stub.callArg](./call-arg) --- --- url: /concepts/stubs/api/calls-arg.md description: >- Causes the stub to call the argument at the provided `index` as a callback function. --- # `stub.callsArg(index)` Causes the stub to call the argument at the provided `index` as a callback function. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.callsArg - basic usage", (t) => { const stub = sinon.stub().callsArg(0); const callback = sinon.fake(); stub(callback); t.ok(callback.called, "callback was called"); t.end(); }); tap.test("stub.callsArg - errors", (t) => { const stub = sinon.stub().callsArg(0); t.throws( () => stub(), /callsArg failed: 1 arguments required but only 0 present/, "throws when no arguments provided" ); t.throws( () => stub("definitely not a function"), /argument at index 0 is not a function/, "throws when argument is not a function" ); t.end(); }); ``` ## Errors When the argument at the provided `index` is not available or is not a function, an `Error` will be thrown. ## See also * [stub.callsArgAsync](./calls-arg-async) * [stub.callsArgOn](./calls-arg-on) * [stub.callsArgOnAsync](./calls-arg-on-async) * [stub.callsArgOnWith](./calls-arg-on-with) * [stub.callsArgOnWithAsync](./calls-arg-on-with-async) * [stub.callsArgWith](./calls-arg-with) * [stub.callsArgWithAsync](./calls-arg-with-async) --- --- url: /concepts/stubs/api/calls-arg-async.md description: >- Causes the stub to call the argument at the provided `index` as a callback function, asynchronously. --- # `stub.callsArgAsync(index)` Causes the stub to call the argument at the provided `index` as a callback function, asynchronously. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.callsArgAsync - basic usage", async (t) => { const clock = sinon.useFakeTimers(); const stub = sinon.stub().callsArgAsync(0); let value = 0; function updateValue() { value = 1; } stub(updateValue); t.equal(value, 0, "value is 0 immediately after stub call"); await clock.tickAsync(1); t.equal(value, 1, "value is 1 after async callback"); clock.restore(); t.end(); }); tap.test("stub.callsArgAsync - errors", (t) => { const stub = sinon.stub().callsArgAsync(0); const pie = "apple pie"; t.throws( () => stub(pie), /argument at index 0 is not a function/, "throws when argument is not a function" ); t.end(); }); ``` ## Errors When the argument at the provided `index` is `undefined`, or not a function, an `Error` will be thrown. ## See also * [stub.callsArg](./calls-arg) * [stub.callsArgOn](./calls-arg-on) * [stub.callsArgOnAsync](./calls-arg-on-async) * [stub.callsArgOnWith](./calls-arg-on-with) * [stub.callsArgOnWithAsync](./calls-arg-on-with-async) * [stub.callsArgWith](./calls-arg-with) * [stub.callsArgWithAsync](./calls-arg-with-async) ## More information * https://nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick * https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop * https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout * YouTube: ["JavaScript Visualized - Event Loop, Web APIs, (Micro)task Queue"](https://www.youtube.com/watch?v=eiC58R16hb8) by Lydia Hallie * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this --- --- url: /concepts/stubs/api/calls-arg-on.md description: >- Causes the stub to call the argument at the provided `index` as a callback function, with an additional `object` parameter to pass the [`this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) context. --- # `stub.callsArgOn(index, object)` Causes the stub to call the argument at the provided `index` as a callback function, with an additional `object` parameter to pass the [`this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) context. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.callsArgOn - basic usage", (t) => { const person = { name: "Mickey Mouse" }; const stub = sinon.stub().callsArgOn(0, person); let capturedThis; function hello() { capturedThis = this; } stub(hello); t.equal(capturedThis, person, "callback called with person as this"); t.equal(capturedThis.name, "Mickey Mouse", "this.name is Mickey Mouse"); t.end(); }); tap.test("stub.callsArgOn - errors", (t) => { const person = { name: "Mickey Mouse" }; const stub = sinon.stub().callsArgOn(0, person); t.throws( () => stub(), /callsArg failed: 1 arguments required but only 0 present/, "throws when no arguments provided" ); t.throws( () => stub(undefined), /argument at index 0 is not a function/, "throws when argument is not a function" ); t.end(); }); ``` ## Errors When the argument at the provided `index` is not available or is not a function, an `Error` will be thrown. ## See also * [stub.callsArg](./calls-arg) * [stub.callsArgAsync](./calls-arg-async) * [stub.callsArgOnAsync](./calls-arg-on-async) * [stub.callsArgOnWith](./calls-arg-on-with) * [stub.callsArgOnWithAsync](./calls-arg-on-with-async) * [stub.callsArgWith](./calls-arg-with) * [stub.callsArgWithAsync](./calls-arg-with-async) * [this](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) --- --- url: /concepts/stubs/api/calls-arg-on-with.md description: >- Causes the stub to call the argument at the provided `index` as a callback function, with the argument(s) provided and an additional `object` parameter to pass the [`this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this --- # `stub.callsArgOnWith(index, object)` Causes the stub to call the argument at the provided `index` as a callback function, with the argument(s) provided and an additional `object` parameter to pass the [`this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) context. ```js import * as sinon from "sinon"; const person = { name: "Mickey Mouse" }; const stub = sinon .stub() .callsArgOnWith(0, person, "apple", "banana", "cherry"); function hello(first, second, third) { console.log(this.name); console.log(first, second, third); } stub(hello); // => Mickey Mouse // => apple banana cherry ``` ## Errors When the argument at the provided `index is not available or is not a function, an `Error\` will be thrown. ```js import * as sinon from "sinon"; const person = { name: "Mickey Mouse" }; const stub = sinon .stub() .callsArgOnWith(0, person, "apple", "banana", "cherry"); function hello(first, second, third) { console.log(this.name); console.log(first, second, third); } stub(undefined); // => Uncaught TypeError: argument at index 0 is not a function: undefined ``` ## See also * [stub.callsArg](./calls-arg) * [stub.callsArgAsync](./calls-arg-async) * [stub.callsArgOn](./calls-arg-on) * [stub.callsArgOnAsync](./calls-arg-on-async) * [stub.callsArgOnWithAsync](./calls-arg-on-with-async) * [stub.callsArgWith](./calls-arg-with) * [stub.callsArgWithAsync](./calls-arg-with-async) --- --- url: /concepts/stubs/api/calls-arg-on-with-async.md description: >- Causes the stub to call the argument at the provided index as a callback function, with the argument(s) provided and an additional `object` parameter to pass the [`this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this. --- # `stub.callsArgOnWithAsync(index, object)` Causes the stub to call the argument at the provided index as a callback function, with the argument(s) provided and an additional `object` parameter to pass the [`this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) context, asynchronously. ```js import * as sinon from "sinon"; const person = { name: "Mickey Mouse" }; const stub = sinon.stub().callsArgOnWithAsync(0, person, "Donald Duck"); function rename(newName) { this.name = newName; } stub(rename); console.log(person.name); // => "Mickey Mouse" setTimeout(function () { console.log(person.name); // => "Donald Duck" }, 1); ``` ## Errors When the argument at the provided `index` is not available or is not a function, an `Error` will be thrown. ```js import * as sinon from "sinon"; const person = { name: "Mickey Mouse" }; const stub = sinon.stub().callsArgOnWithAsync(0, person, "Donald Duck"); function rename(newName) { this.name = newName; } stub(undefined); // => Uncaught TypeError: argument at index 0 is not a function: undefined ``` ## See also * [stub.callsArg](./calls-arg) * [stub.callsArgAsync](./calls-arg-async) * [stub.callsArgOn](./calls-arg-on) * [stub.callsArgOnAsync](./calls-arg-on-async) * [stub.callsArgOnWith](./calls-arg-on-with) * [stub.callsArgWith](./calls-arg-with) * [stub.callsArgWithAsync](./calls-arg-with-async) ## More information * https://nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick * https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop * https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout * YouTube: ["JavaScript Visualized - Event Loop, Web APIs, (Micro)task Queue"](https://www.youtube.com/watch?v=eiC58R16hb8) by Lydia Hallie * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this --- --- url: /concepts/stubs/api/calls-arg-with.md description: >- Causes the stub to call the argument at the provided `index` as a callback function, with the argument(s) provided. --- # `stub.callsArgWith(index)` Causes the stub to call the argument at the provided `index` as a callback function, with the argument(s) provided. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.callsArgWith - basic usage", (t) => { const stub = sinon.stub().callsArgWith(0, "apple", "banana", "cherry"); const callback = sinon.fake(); stub(callback); t.ok(callback.calledOnce, "callback was called once"); t.ok( callback.calledWith("apple", "banana", "cherry"), "callback was called with provided arguments" ); t.end(); }); tap.test("stub.callsArgWith - errors", (t) => { const stub = sinon.stub().callsArgWith(0, "apple", "banana", "cherry"); t.throws( () => stub(undefined), /argument at index 0 is not a function/, "throws when argument is not a function" ); t.end(); }); ``` ## Errors When the argument at the provided `index` is not available or is not a function, an `Error` will be thrown. ## See also * [stub.callsArg](./calls-arg) * [stub.callsArgAsync](./calls-arg-async) * [stub.callsArgOn](./calls-arg-on) * [stub.callsArgOnAsync](./calls-arg-on-async) * [stub.callsArgOnWith](./calls-arg-on-with) * [stub.callsArgOnWithAsync](./calls-arg-on-with-async) * [stub.callsArgWithAsync](./calls-arg-with-async) --- --- url: /concepts/stubs/api/calls-arg-with-async.md description: >- Causes the stub to call the argument at the provided `index` as a callback function, with the argument(s) provided, asynchronously. --- # `stub.callsArgWithAsync(index)` Causes the stub to call the argument at the provided `index` as a callback function, with the argument(s) provided, asynchronously. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.callsArgWithAsync - basic usage", async (t) => { const clock = sinon.useFakeTimers(); const index = 0; const arg1 = 1; const stub = sinon.stub().callsArgWithAsync(index, arg1); let value = 0; function updateValue(newValue) { value = newValue; } stub(updateValue); t.equal(value, 0, "value is 0 immediately"); await clock.tickAsync(1); t.equal(value, 1, "value is 1 after async callback"); clock.restore(); t.end(); }); tap.test("stub.callsArgWithAsync - errors", (t) => { const index = 0; const arg1 = 1; const stub = sinon.stub().callsArgWithAsync(index, arg1); t.throws( () => stub(undefined), /argument at index 0 is not a function/, "throws when argument is not a function" ); t.end(); }); ``` ## Errors When the argument at the provided `index` is not available or is not a function, an `Error` will be thrown. ## See also * [stub.callsArg](./calls-arg) * [stub.callsArgAsync](./calls-arg-async) * [stub.callsArgOn](./calls-arg-on) * [stub.callsArgOnAsync](./calls-arg-on-async) * [stub.callsArgOnWith](./calls-arg-on-with) * [stub.callsArgOnWithAsync](./calls-arg-on-with-async) * [stub.callsArgWith](./calls-arg-with) ## More information * https://nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick * https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop * https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout * YouTube: ["JavaScript Visualized - Event Loop, Web APIs, (Micro)task Queue"](https://www.youtube.com/watch?v=eiC58R16hb8) by Lydia Hallie * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this --- --- url: /concepts/stubs/api/calls-fake.md description: Makes the stub call the provided `fakeFunction` when invoked. --- # `stub.callsFake(f)` Makes the stub call the provided `fakeFunction` when invoked. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.callsFake", (t) => { const myObj = {}; myObj.prop = function propFn() { return "foo"; }; function f() { return "bar"; } sinon.stub(myObj, "prop").callsFake(f); t.equal(myObj.prop(), "bar", "stub calls the fake function"); sinon.restore(); t.end(); }); ``` --- --- url: /concepts/stubs/api/calls-through.md description: >- Causes the original method wrapped into the stub to be called when none of the conditional stubs are matched. --- # `stub.callsThrough();` Causes the original method wrapped into the stub to be called when none of the conditional stubs are matched. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.callsThrough", (t) => { const obj = {}; obj.sum = function sum(a, b) { return a + b; }; sinon .stub(obj, "sum") .withArgs(2, 2) .callsFake(function foo() { return "bar"; }); obj.sum.callThrough(); t.equal( obj.sum(2, 2), "bar", "stub returns fake value for matched arguments" ); t.equal( obj.sum(1, 2), 3, "stub calls through to original for unmatched arguments" ); sinon.restore(); t.end(); }); ``` --- --- url: /concepts/stubs/api/call-through-with-new.md description: >- Causes the original method wrapped into the stub to be called using the `new` operator, when none of the conditional stubs are matched. --- # `stub.callThroughWithNew()` Causes the original method wrapped into the stub to be called using the `new` operator, when none of the conditional stubs are matched. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.callThroughWithNew - basic usage", (t) => { const obj = {}; obj.Sum = function MyConstructor(a, b) { this.result = a + b; }; sinon .stub(obj, "Sum") .callThroughWithNew() .withArgs(1, 2) .returns({ result: 9000 }); const sum1 = new obj.Sum(2, 2); t.equal(sum1.result, 4, "calls through to original constructor"); const sum2 = new obj.Sum(1, 2); t.equal(sum2.result, 9000, "returns custom value for matching args"); obj.Sum.restore(); t.end(); }); ``` --- --- url: /concepts/stubs/api/get.md description: Replaces a getter for an object property. --- # `stub.get(getterFn)` Replaces a getter for an object property. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.get", (t) => { const myObj = { prop: "foo" }; sinon.stub(myObj, "prop").get(function getterFn() { return "bar"; }); t.equal(myObj.prop, "bar", "getter returns stubbed value"); sinon.restore(); t.end(); }); ``` ## More information * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get --- --- url: /concepts/stubs/api/on-call.md description: >- Defines the behavior of the stub on the _nth_ call. Useful for testing sequential interactions. --- # `stub.onCall(index)` Defines the behavior of the stub on the *nth* call. Useful for testing sequential interactions. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.onCall - basic usage", (t) => { const callback = sinon.stub(); callback.onCall(0).returns("Apple pie"); callback.onCall(1).returns("Blueberry pie"); callback.returns("Raspberry pie"); t.equal(callback(), "Apple pie", "first call returns Apple pie"); t.equal(callback(), "Blueberry pie", "second call returns Blueberry pie"); t.equal(callback(), "Raspberry pie", "third call returns Raspberry pie"); t.equal( callback(), "Raspberry pie", "all following calls return Raspberry pie" ); t.end(); }); ``` There are convenience methods [`onFirstCall`](./on-first-call), [`onSecondCall`](./on-second-call), [`onThirdCall`](./on-third-call) to improve readability of stub definitions. `onCall` can be combined with all of the behavior defining methods in this API. In particular, it can be used together with `withArgs`. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.onCall with withArgs", (t) => { const callback = sinon.stub(); const FORTY_TWO = 42; const UNKNOWN_VALUE = "any unknown value"; callback .withArgs(FORTY_TWO) .onFirstCall() .returns("Apple pie") .onSecondCall() .returns("Blueberry pie"); callback.returns("Raspberry pie"); t.equal( callback(UNKNOWN_VALUE), "Raspberry pie", "unknown value returns default" ); t.equal( callback(FORTY_TWO), "Apple pie", "first call with 42 returns Apple pie" ); t.equal( callback(UNKNOWN_VALUE), "Raspberry pie", "unknown value returns default" ); t.equal( callback(FORTY_TWO), "Blueberry pie", "second call with 42 returns Blueberry pie" ); t.equal( callback(FORTY_TWO), "Raspberry pie", "third call with 42 falls back to default" ); t.end(); }); ``` Note how the behavior of the stub for argument `FORTY_TWO` falls back to the default behavior once no more calls have been defined. --- --- url: /concepts/stubs/api/on-first-call.md description: '`onFirstCall` is an alias for [`onCall(0)`](./on-call).' --- # `stub.onFirstCall()` `onFirstCall` is an alias for [`onCall(0)`](./on-call). ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.onFirstCall", (t) => { const callback = sinon.stub(); callback.onFirstCall().returns("Apple pie"); callback.returns("Raspberry pie"); t.equal(callback(), "Apple pie", "first call returns Apple pie"); t.equal(callback(), "Raspberry pie", "second call returns Raspberry pie"); t.equal( callback(), "Raspberry pie", "all following calls return Raspberry pie" ); t.end(); }); ``` ## See also * [`onSecondCall`](./on-second-call) * [`onThirdCall`](./on-third-call) --- --- url: /concepts/stubs/api/on-second-call.md description: '`onSecondCall` is an alias for [`onCall(1)`](./on-call).' --- # `stub.onSecondCall()` `onSecondCall` is an alias for [`onCall(1)`](./on-call). ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.onSecondCall", (t) => { const callback = sinon.stub(); callback.onSecondCall().returns("Apple pie"); callback.returns("Raspberry pie"); t.equal(callback(), "Raspberry pie", "first call returns Raspberry pie"); t.equal(callback(), "Apple pie", "second call returns Apple pie"); t.equal( callback(), "Raspberry pie", "all following calls return Raspberry pie" ); t.end(); }); ``` ## See also * [`onFirstCall`](./on-first-call) * [`onThirdCall`](./on-third-call) --- --- url: /concepts/stubs/api/on-third-call.md description: '`onThirdCall` is an alias for [`onCall(2)`](./on-call).' --- # `stub.onThirdCall()` `onThirdCall` is an alias for [`onCall(2)`](./on-call). ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.onThirdCall", (t) => { const callback = sinon.stub(); callback.onThirdCall().returns("Apple pie"); callback.returns("Raspberry pie"); t.equal(callback(), "Raspberry pie", "first call returns Raspberry pie"); t.equal(callback(), "Raspberry pie", "second call returns Raspberry pie"); t.equal(callback(), "Apple pie", "third call returns Apple pie"); t.equal( callback(), "Raspberry pie", "all following calls return Raspberry pie" ); t.end(); }); ``` ## See also * [`onFirstCall`](./on-first-call) * [`onSecondCall`](./on-second-call) --- --- url: /concepts/stubs/api/rejects.md description: >- --- # `stub.rejects()` Causes the stub to return a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) which rejects. ## `stub.rejects();` Causes the stub to return a Promise which rejects with an [`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error). Use this when you don't care about what the value of the error is. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.rejects() - no arguments", async (t) => { const stub = sinon.stub().rejects(); try { await stub(); t.fail("should have rejected"); } catch (error) { t.ok(error instanceof Error, "rejects with an Error"); t.equal(error.message, "Error", "error message is 'Error'"); } t.end(); }); ``` ## `stub.rejects(errorName);` Causes the stub to return a Promise which rejects with an [`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error), with `name` property set to the `errorName` argument and a blank `message` property. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.rejects(errorName)", async (t) => { const stub = sinon.stub().rejects("apple pie"); try { await stub(); t.fail("should have rejected"); } catch (error) { t.equal(error.name, "apple pie", "error name is set"); t.equal(error.message, "", "error message is blank"); } t.end(); }); ``` ## `stub.rejects(errorName, errorMessage);` Causes the stub to return a Promise which rejects with an [`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error), with `name` property set to the provided `errorName` and the `message` property set to the `errorMessage` argument. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.rejects(errorName, errorMessage)", async (t) => { const stub = sinon.stub().rejects("some error name", "the pie is a lie"); try { await stub(); t.fail("should have rejected"); } catch (error) { t.equal(error.name, "some error name", "error name is set"); t.equal(error.message, "the pie is a lie", "error message is set"); } t.end(); }); ``` ## `stub.rejects(error);` When called with an [`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) instance, it causes the stub to return a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) which rejects with the provided error instance. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.rejects(error)", async (t) => { const pieError = new RangeError("The pie is a lie"); const stub = sinon.stub().rejects(pieError); try { await stub(); t.fail("should have rejected"); } catch (error) { t.equal(error, pieError, "rejects with the exact error instance"); } t.end(); }); ``` ## Note When constructing the Promise, sinon uses the `Promise.reject` method. --- --- url: /concepts/stubs/api/reset.md description: Resets both behavior and history of the stub. --- # `stub.reset()` Resets both behavior and history of the stub. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.reset", (t) => { const stub = sinon.stub(); t.equal(stub(), undefined, "stub returns undefined initially"); stub.returns("Apple pie"); t.equal(stub(), "Apple pie", "stub returns Apple pie after configuration"); stub.reset(); t.equal(stub(), undefined, "stub returns undefined after reset"); t.end(); }); ``` This is equivalent to calling both [`stub.resetBehavior()`](./reset-behavior) and [`stub.resetHistory()`](./reset-history). As a convenience, you can apply `stub.reset()` to all stubs using [`sinon.reset()`](/concepts/sandboxes/api/reset). --- --- url: /concepts/stubs/api/reset-behavior.md description: Resets the stub's behavior to the default behavior --- # `stub.resetBehavior()` Resets the stub's behavior to the default behavior ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.resetBehavior", (t) => { const stub = sinon.stub(); stub.returns(54); t.equal(stub(), 54, "stub returns 54"); stub.resetBehavior(); t.equal(typeof stub(), "undefined", "stub returns undefined after reset"); t.end(); }); ``` See also: * [`stub.reset`](./reset) * [`stub.resetHistory`](./reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/stubs/api/reset-history.md description: Resets the stub's history --- # `stub.resetHistory()` Resets the stub's history ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.resetHistory", (t) => { const stub = sinon.stub(); t.notOk(stub.called, "stub.called is false initially"); stub(); t.ok(stub.called, "stub.called is true after call"); stub.resetHistory(); t.notOk(stub.called, "stub.called is false after resetHistory"); t.end(); }); ``` See also: * [`stub.reset`](./reset) * [`stub.resetBehavior`](./reset-behavior) * [`sinon.resetHistory`](/concepts/sandboxes/api/reset-history) * [`sinon.reset`](/concepts/sandboxes/api/reset) --- --- url: /concepts/stubs/api/resolves.md description: >- Causes the stub to return a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), which resolves to the provided value. --- # `stub.resolves(value)` Causes the stub to return a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), which resolves to the provided value. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.resolves", async (t) => { const stub = sinon.stub().resolves("apple pie"); const result = await stub(); t.equal(result, "apple pie", "stub resolves with the provided value"); t.end(); }); ``` --- --- url: /concepts/stubs/api/resolves-arg.md description: >- Causes the stub to return a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), which resolves to the argument at the --- # `stub.resolvesArg(index)` Causes the stub to return a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), which resolves to the argument at the provided index. `stub.resolvesArg(0);` causes the stub to return a Promise, which resolves to the first argument. If the argument at the provided index is not available, a `TypeError` will be thrown. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.resolvesArg", async (t) => { const stub = sinon.stub().resolvesArg(1); const result = await stub("apple pie", "blueberry pie", "cherry pie"); t.equal(result, "blueberry pie", "resolves with argument at index 1"); try { await stub("apple pie"); t.fail("should have rejected"); } catch (error) { t.match( error.message, /resolvesArg failed: 2 arguments required but only 1 present/, "rejects when argument not available" ); } t.end(); }); ``` --- --- url: /concepts/stubs/api/returns.md description: Makes the stub return the provided value. --- # `stub.returns(value)` Makes the stub return the provided value. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.returns", (t) => { const stub = sinon.stub(); t.equal(stub(), undefined, "stub returns undefined by default"); stub.returns("Apple pie"); t.equal(stub(), "Apple pie", "stub returns the provided value"); t.end(); }); ``` --- --- url: /concepts/stubs/api/returns-this.md description: Causes the stub to return its this value. --- # `stub.returnsThis()` Causes the stub to return its this value. Useful for stubbing fluent APIs (jQuery style). ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.returnsThis", (t) => { const myObj = { one: function () {}, two: function () { return "apple pie"; } }; const stub = sinon.stub(myObj, "one").returnsThis(); t.equal(myObj.one().two(), "apple pie", "stub returns this for chaining"); stub.restore(); t.end(); }); ``` --- --- url: /concepts/stubs/api/set.md description: Replaces a setter for an object property. --- # `stub.set(setterFn)` Replaces a setter for an object property. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.set", (t) => { const myObj = { example: "oldValue", prop: "foo" }; sinon.stub(myObj, "prop").set(function setterFn(val) { myObj.example = val; }); myObj.prop = "baz"; t.equal(myObj.example, "baz", "setter updates the example property"); sinon.restore(); t.end(); }); ``` ## More information * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set --- --- url: /concepts/stubs/api/throws.md description: Causes the stub to throw an Error. --- # `stub.throws()` Causes the stub to throw an Error. ## `stub.throws(message)` ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.throws(message)", (t) => { const stub = sinon.stub(); stub.throws("The pie is a lie"); t.throws(() => stub(), /The pie is a lie/, "stub throws error with message"); t.end(); }); ``` ## `stub.throws("name" [, "optional message"])` Causes the stub to throw an exception with the `name` property set to the provided string. The message parameter is optional and will set the `message` property of the exception. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.throws(name, message)", (t) => { const stub = sinon.stub(); stub.throws("PieError", "The pie is a lie"); try { stub(); t.fail("should have thrown"); } catch (error) { t.equal(error.name, "PieError", "error name is set"); t.equal(error.message, "The pie is a lie", "error message is set"); } t.end(); }); ``` ## `stub.throws(obj)` Causes the stub to throw the provided error object. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.throws(obj)", (t) => { const stub = sinon.stub(); stub.throws(new RangeError("The pie is a lie")); t.throws(() => stub(), RangeError, "stub throws RangeError"); t.end(); }); ``` ## `stub.throws(function() { return new Error(); })` Causes the stub to throw the exception returned by the function. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.throws(function)", (t) => { const stub = sinon.stub(); stub.throws(function () { return new SyntaxError("The pie is a lie"); }); t.throws(() => stub(), SyntaxError, "stub throws SyntaxError from function"); t.end(); }); ``` --- --- url: /concepts/stubs/api/throws-arg.md description: Causes the stub to throw the argument at the provided `index. --- # `stub.throwsArg(index)` Causes the stub to throw the argument at the provided \`index. `stub.throwsArg(0);` causes the stub to throw the first argument as the exception. If the argument at the provided index is not available, a `TypeError` will be thrown. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.throwsArg", (t) => { const stub1 = sinon.stub(); stub1.throwsArg(0); try { stub1("Apple pie"); t.fail("should have thrown"); } catch (error) { t.equal(error, "Apple pie", "throws the first argument"); } const stub2 = sinon.stub(); stub2.throwsArg(42); try { stub2("Apple pie"); t.fail("should have thrown"); } catch (error) { t.match( error.message, /throwsArg failed: 43 arguments required but only 1 present/, "throws TypeError when argument not available" ); } t.end(); }); ``` --- --- url: /concepts/stubs/api/value.md description: Defines a new value for this stub. --- # `stub.value(newVal)` Defines a new value for this stub. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.value - basic usage", (t) => { const myObj = { example: "oldValue" }; sinon.stub(myObj, "example").value("newValue"); t.equal(myObj.example, "newValue", "property has new value"); sinon.restore(); t.end(); }); ``` ## Restoring values You can restore values by calling the `restore` method: ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.value - restoring values", (t) => { const myObj = { example: "oldValue" }; const stub = sinon.stub(myObj, "example").value("newValue"); t.equal(myObj.example, "newValue", "property has new value"); stub.restore(); t.equal(myObj.example, "oldValue", "property restored to old value"); t.end(); }); ``` --- --- url: /concepts/stubs/api/with-args.md description: Stubs the method only for the provided arguments. --- # `stub.withArgs()` Stubs the method only for the provided arguments. This is useful to be more expressive in your assertions, where you can access the spy with the same call. It is also useful to create a stub that can act differently in response to different arguments. Uses deep comparison for objects and arrays. Use `stub.withArgs(sinon.match.same(obj))` for strict comparison (see [matchers](/concepts/matchers/)). ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.withArgs", (t) => { const callback = sinon.stub(); callback.withArgs(42).returns(1); callback.withArgs(1).throws(new Error("apple pie")); // No return value, no exception t.equal(callback(), undefined, "returns undefined for unmatched arguments"); t.equal(callback(42), 1, "returns 1 for argument 42"); t.equal(callback.withArgs(42).callCount, 1, "withArgs(42) was called once"); try { callback(1); t.fail("should have thrown"); } catch (error) { t.equal(error.message, "apple pie", "throws error for argument 1"); } t.end(); }); ``` --- --- url: /concepts/stubs/api/yield.md description: Invoke callbacks passed to the `stub` with the given argument(s). --- # `stub.yield()` Invoke callbacks passed to the `stub` with the given argument(s). ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.yield - basic usage", (t) => { const stub = sinon.stub(); let greeting; function callback(name) { greeting = `Hello ${name}`; } stub(callback); stub.yield("Mickey Mouse"); t.equal( greeting, "Hello Mickey Mouse", "callback called with correct argument" ); t.end(); }); ``` If the stub was never called with a function argument, `yield` throws an error. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.yield - error when no callback passed", (t) => { const stub = sinon.stub(); stub("not a function argument"); t.throws( () => stub.yield("Mickey Mouse"), /stub cannot yield since no callback was passed/, "throws error when no callback was passed" ); t.end(); }); ``` Returns an Array with all callbacks return values, in the order the callbacks were called. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.yield - returns array of callback return values", (t) => { const stub = sinon.stub(); function callback1(name) { return `Hello ${name}`; } function callback2(name) { return `Goodbye ${name}`; } stub(callback1); stub(callback2); const result = stub.yield("Mickey Mouse"); t.same( result, ["Hello Mickey Mouse", "Goodbye Mickey Mouse"], "returns array with all callback return values" ); t.end(); }); ``` `yield` is aliased as `invokeCallback`. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.invokeCallback - alias for yield", (t) => { const stub = sinon.stub(); let greeting; function callback(name) { greeting = `Hello ${name}`; } stub(callback); stub.invokeCallback("Mickey Mouse"); t.equal( greeting, "Hello Mickey Mouse", "invokeCallback works as alias for yield" ); t.end(); }); ``` ## See also * [`yieldTo`](./yield-to) --- --- url: /concepts/stubs/api/yield-to.md description: Invokes callbacks passed as a property name on an object to the stub. --- # `stub.yieldTo()` Invokes callbacks passed as a property name on an object to the stub. Like [`yield`](./yield), `yieldTo` grabs the first matching argument, finds the callback and calls it with the (optional) arguments. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.yieldTo - basic usage", (t) => { const stub = sinon.stub(); let actual; const object = { success() { actual = "Success!"; }, failure(errorCode) { actual = `${errorCode}: Oh noes!`; } }; // call the stub with the object stub(object); // define a property name to yield to const successPropertyName = "success"; stub.yieldTo(successPropertyName); // evaluate the result t.equal(actual, "Success!", "success callback was called"); // define a property name to yield to const failurePropertyName = "failure"; // define the optional argument const errorCode = "429"; stub.yieldTo(failurePropertyName, errorCode); // evaluate the result t.equal( actual, "429: Oh noes!", "failure callback was called with error code" ); t.end(); }); ``` --- --- url: /concepts/stubs/api/yields.md description: >- Causes the stub to call the first callback it receives with any provided arguments. --- # `stub.yields()` Causes the stub to call the first callback it receives with any provided arguments. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.yields - basic usage without arguments", (t) => { let called = false; function bake() { called = true; } const bakeStub = sinon.stub().yields(); bakeStub(bake); t.ok(called, "callback was called"); t.end(); }); tap.test("stub.yields - with arguments", (t) => { let filling; function assemble(f) { filling = f; } const assembleStub = sinon.stub().yields("raspberry"); assembleStub(assemble); t.equal(filling, "raspberry", "callback called with raspberry"); t.end(); }); ``` If a method accepts more than one callback, you need to use [`yieldsRight`](./yields-right) to call the last callback or [`callsArg`](./calls-arg) to have the stub invoke other callbacks than the first or last one. ## See also * [stub.yieldsAsync](./yields-async) * [stub.yieldsOn](./yields-on) * [stub.yieldsOnAsync](./yields-on-async) * [stub.yieldsRight](./yields-right) * [stub.yieldsTo](./yields-to) * [stub.yieldsToOn](./yields-to-on) * [stub.yieldsToAsync](./yields-to-async) * [stub.yieldsToOnAsync](./yields-to-on-async) --- --- url: /concepts/stubs/api/yields-async.md description: >- Causes the stub to call the first callback it receives with any provided arguments, asynchronously. --- # `stub.yieldsAsync()` Causes the stub to call the first callback it receives with any provided arguments, asynchronously. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.yieldsAsync - basic usage", async (t) => { const clock = sinon.useFakeTimers(); const stub = sinon.stub().yieldsAsync(); let value = 0; function updateValue() { value = 1; } stub(updateValue); t.equal(value, 0, "value is 0 immediately"); await clock.tickAsync(1); t.equal(value, 1, "value is 1 after async callback"); clock.restore(); t.end(); }); ``` If a method accepts more than one callback, you need to use [`yieldsRight`](./yields-right) to call the last callback or [`callsArg`](./calls-arg) to have the stub invoke other callbacks than the first or last one. ## See also * [stub.yields](./yields) * [stub.yieldsOn](./yields-on) * [stub.yieldsOnAsync](./yields-on-async) * [stub.yieldsRight](./yields-right) * [stub.yieldsTo](./yields-to) * [stub.yieldsToOn](./yields-to-on) * [stub.yieldsToAsync](./yields-to-async) * [stub.yieldsToOnAsync](./yields-to-on-async) --- --- url: /concepts/stubs/api/yields-on.md description: >- Causes the stub to call the first callback it receives with any provided arguments, with an additional parameter to pass the [`this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this --- # `stub.yieldsOn()` Causes the stub to call the first callback it receives with any provided arguments, with an additional parameter to pass the [`this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) context. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.yieldsOn - basic usage", (t) => { const person = { name: "Mickey Mouse" }; const stub = sinon.stub().yieldsOn(person); let capturedThis; function hello() { capturedThis = this; } stub(hello); t.equal(capturedThis, person, "callback called with person as this"); t.equal(capturedThis.name, "Mickey Mouse", "this.name is Mickey Mouse"); t.end(); }); ``` ## See also * [stub.yields](./yields) * [stub.yieldsAsync](./yields-async) * [stub.yieldsOnAsync](./yields-on-async) * [stub.yieldsRight](./yields-right) * [stub.yieldsTo](./yields-to) * [stub.yieldsToOn](./yields-to-on) * [stub.yieldsToAsync](./yields-to-async) * [stub.yieldsToOnAsync](./yields-to-on-async) ## More information * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this --- --- url: /concepts/stubs/api/yields-on-async.md description: >- Causes the stub to call the first callback it receives with any provided arguments, with an additional parameter to pass the [`this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this --- # `stub.yieldsOnAsync()` Causes the stub to call the first callback it receives with any provided arguments, with an additional parameter to pass the [`this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) context, asynchronously. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.yieldsOnAsync - basic usage", async (t) => { const clock = sinon.useFakeTimers(); const car = { color: "red" }; const stub = sinon.stub().yieldsOnAsync(car); function updateColor() { this.color = "blue"; } t.equal(car.color, "red", "car color is red initially"); stub(updateColor); t.equal( car.color, "red", "car color is still red immediately after stub call" ); await clock.tickAsync(1); t.equal(car.color, "blue", "car color is blue after async callback"); clock.restore(); t.end(); }); tap.test("stub.yieldsOnAsync - errors", (t) => { const car = { color: "red" }; const stub = sinon.stub().yieldsOnAsync(car); const pie = "apple pie"; t.throws( () => stub(pie), /stub expected to yield, but no callback was passed/, "throws when argument is not a function" ); t.end(); }); ``` ## Errors When the argument at the provided index is `undefined`, or not a function, an `Error` will be thrown. ## See also * [stub.yields](./yields) * [stub.yieldsAsync](./yields-async) * [stub.yieldsOn](./yields-on) * [stub.yieldsRight](./yields-right) * [stub.yieldsTo](./yields-to) * [stub.yieldsToOn](./yields-to-on) * [stub.yieldsToAsync](./yields-to-async) * [stub.yieldsToOnAsync](./yields-to-on-async) ## More information * https://nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick * https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop * https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout * YouTube: ["JavaScript Visualized - Event Loop, Web APIs, (Micro)task Queue"](https://www.youtube.com/watch?v=eiC58R16hb8) by Lydia Hallie * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this --- --- url: /concepts/stubs/api/yields-right.md description: >- Causes the stub to call the last callback it receives with any provided arguments. --- # `stub.yieldsRight()` Causes the stub to call the last callback it receives with any provided arguments. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.yieldsRight - basic usage without arguments", (t) => { let assembledCalled = false; let bakeCalled = false; function assemble() { assembledCalled = true; } function bake(filling = "apple") { bakeCalled = true; t.equal(filling, "apple", "bake called with default filling"); } const stub = sinon.stub().yieldsRight(); stub(assemble, bake); t.notOk(assembledCalled, "assemble was not called"); t.ok(bakeCalled, "bake was called"); t.end(); }); tap.test("stub.yieldsRight - with arguments", (t) => { let assembledCalled = false; let filling; function assemble() { assembledCalled = true; } function bake(f = "apple") { filling = f; } const stubWithArgs = sinon.stub().yieldsRight("raspberry"); stubWithArgs(assemble, bake); t.notOk(assembledCalled, "assemble was not called"); t.equal(filling, "raspberry", "bake called with raspberry"); t.end(); }); ``` ## See also * [stub.yields](./yields) * [stub.yieldsAsync](./yields-async) * [stub.yieldsOn](./yields-on) * [stub.yieldsOnAsync](./yields-on-async) * [stub.yieldsTo](./yields-to) * [stub.yieldsToOn](./yields-to-on) * [stub.yieldsToAsync](./yields-to-async) * [stub.yieldsToOnAsync](./yields-to-on-async) --- --- url: /concepts/stubs/api/yields-to.md description: >- Causes the spy to invoke a callback passed as a property of an object to the spy. --- # `stub.yieldsTo()` Causes the spy to invoke a callback passed as a property of an object to the spy. `yieldsTo` grabs the first matching argument, finds the callback and calls it with the (optional) arguments. ```js import t from "tap"; import sinon from "sinon"; t.test("stub.yieldsTo invokes callback passed as object property", (t) => { const stub = sinon.stub().yieldsTo("hello", "Mickey Mouse"); const obj = { hello: sinon.fake() }; stub(obj); // Verify the callback was invoked with the correct arguments t.ok(obj.hello.calledOnce, "hello callback should be called once"); t.ok( obj.hello.calledWith("Mickey Mouse"), "hello should be called with 'Mickey Mouse'" ); t.end(); }); ``` ## See also * [stub.yields](./yields) * [stub.yieldsAsync](./yields-async) * [stub.yieldsOn](./yields-on) * [stub.yieldsOnAsync](./yields-on-async) * [stub.yieldsRight](./yields-right) * [stub.yieldsToOn](./yields-to-on) * [stub.yieldsToAsync](./yields-to-async) * [stub.yieldsToOnAsync](./yields-to-on-async) --- --- url: /concepts/stubs/api/yields-to-async.md description: >- Causes the spy to invoke a callback passed as a property of an object to the spy. --- # `stub.yieldsToAsync()` Causes the spy to invoke a callback passed as a property of an object to the spy. `yieldsToAsync` grabs the first matching argument, finds the callback and calls it with the (optional) arguments, asynchronously. ```js import t from "tap"; import sinon from "sinon"; t.test("stub.yieldsToAsync invokes callback asynchronously", async (t) => { const clock = sinon.useFakeTimers(); const stub = sinon.stub().yieldsToAsync("setColor", "blue"); const car = { color: "red", setColor: sinon.fake(function (newColor) { car.color = newColor; }) }; stub(car); // Verify initial state (callback not yet invoked) t.equal(car.color, "red", "color should still be red initially"); t.notOk(car.setColor.called, "setColor should not be called yet"); // Advance time to trigger async callback await clock.tickAsync(1); // Verify callback was invoked asynchronously t.ok(car.setColor.calledOnce, "setColor should be called once"); t.ok( car.setColor.calledWith("blue"), "setColor should be called with 'blue'" ); t.equal(car.color, "blue", "color should be blue after async callback"); clock.restore(); t.end(); }); ``` ## See also * [stub.yields](./yields) * [stub.yieldsAsync](./yields-async) * [stub.yieldsOn](./yields-on) * [stub.yieldsOnAsync](./yields-on-async) * [stub.yieldsRight](./yields-right) * [stub.yieldsTo](./yields-to) * [stub.yieldsToOn](./yields-to-on) * [stub.yieldsToOnAsync](./yields-to-on-async) ## More information * https://nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick * https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop * https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout * YouTube: ["JavaScript Visualized - Event Loop, Web APIs, (Micro)task Queue"](https://www.youtube.com/watch?v=eiC58R16hb8) by Lydia Hallie --- --- url: /concepts/stubs/api/yields-to-on.md description: >- Causes the spy to invoke a callback passed as a property of an object to the spy. --- # `stub.yieldsToOn()` Causes the spy to invoke a callback passed as a property of an object to the spy. `yieldsToOn` grabs the first matching argument, finds the callback and calls it, passing the [`this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) context and any (optional) arguments. ```js import t from "tap"; import sinon from "sinon"; t.test("stub.yieldsToOn invokes callback with specified this context", (t) => { const obj = { hello: sinon.fake() }; const obj2 = { item: "Apple Pie" }; const stub = sinon.stub().yieldsToOn("hello", obj2); stub(obj); // Verify the callback was invoked with correct context t.ok(obj.hello.calledOnce, "hello callback should be called once"); t.ok( obj.hello.calledOn(obj2), "hello should be called with obj2 as this context" ); t.equal(obj.hello.firstCall.thisValue, obj2, "this should be obj2"); t.end(); }); ``` ## See also * [stub.yields](./yields) * [stub.yieldsAsync](./yields-async) * [stub.yieldsOn](./yields-on) * [stub.yieldsOnAsync](./yields-on-async) * [stub.yieldsRight](./yields-right) * [stub.yieldsTo](./yields-to) * [stub.yieldsToAsync](./yields-to-async) * [stub.yieldsToOnAsync](./yields-to-on-async) ## More information * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this --- --- url: /concepts/stubs/api/yields-to-on-async.md description: >- Causes the spy to invoke a callback passed as a property of an object to the spy. --- # `stub.yieldsToOnAsync(property, context, [arg1, arg2, ...])` Causes the spy to invoke a callback passed as a property of an object to the spy. `yieldsToOnAsync` grabs the first matching argument, finds the callback and calls it, passing the [`this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) context and any (optional) arguments, asynchronously. ```js import t from "tap"; import sinon from "sinon"; t.test( "stub.yieldsToOnAsync invokes callback asynchronously with context and args", async (t) => { const clock = sinon.useFakeTimers(); const obj = { setItem: sinon.fake(function (newItem) { this.item = newItem; }) }; const obj2 = { item: "Apple Pie" }; const stub = sinon .stub() .yieldsToOnAsync("setItem", obj2, "Sweet potato pie"); stub(obj); // Verify initial state (callback not yet invoked) t.notOk(obj.setItem.called, "setItem should not be called yet"); t.equal( obj2.item, "Apple Pie", "item should still be 'Apple Pie' initially" ); // Advance time to trigger async callback await clock.tickAsync(1); // Verify callback was invoked asynchronously with correct context and arguments t.ok(obj.setItem.calledOnce, "setItem should be called once"); t.ok( obj.setItem.calledOn(obj2), "setItem should be called with obj2 as this context" ); t.ok( obj.setItem.calledWith("Sweet potato pie"), "setItem should be called with 'Sweet potato pie'" ); t.equal( obj2.item, "Sweet potato pie", "item should be updated to 'Sweet potato pie'" ); clock.restore(); t.end(); } ); ``` ## See also * [stub.yields](./yields) * [stub.yieldsAsync](./yields-async) * [stub.yieldsOn](./yields-on) * [stub.yieldsOnAsync](./yields-on-async) * [stub.yieldsRight](./yields-right) * [stub.yieldsTo](./yields-to) * [stub.yieldsToOn](./yields-to-on) * [stub.yieldsToAsync](./yields-to-async) ## More information * https://nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick * https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop * https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout * YouTube: ["JavaScript Visualized - Event Loop, Web APIs, (Micro)task Queue"](https://www.youtube.com/watch?v=eiC58R16hb8) by Lydia Hallie * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this --- --- url: /concepts/stubs/api/wrapped-method.md description: Holds a reference to the original method/function this stub has wrapped. --- # `stub.wrappedMethod()` Holds a reference to the original method/function this stub has wrapped. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.wrappedMethod", (t) => { const obj = { hello: function hello(name) { return `Hello ${name}`; } }; const s = sinon.stub(obj, "hello").callsFake(function hi(name) { return `Hi ${name}`; }); t.equal( obj.hello("Mickey Mouse"), "Hi Mickey Mouse", "stub returns fake value" ); t.equal( obj.hello.wrappedMethod("Mickey Mouse"), "Hello Mickey Mouse", "wrappedMethod returns original value" ); s.restore(); t.end(); }); ``` --- --- url: /concepts/fakes.md description: >- Simple, immutable test doubles that replace spies and stubs. Records arguments, return values, and exceptions for all calls. --- # Fakes ## Introduction `sinon.fake` allows creation of a `fake` `Function` with the ability to set a default behavior. In Sinon, a `fake` is a `Function` that records arguments, return value, the value of `this` and `Error` thrown (if any) for all of its calls. A fake is an [immutable object][immutable]: once created, its behavior will not change. ### Basic usage ```js import tap from "tap"; import sinon from "sinon"; tap.test("basic fake usage - creates a fake that returns a value", (t) => { // Create a fake that returns a value const fake = sinon.fake.returns(42); // Call it like any function const result = fake(); t.equal(result, 42, "fake returns the configured value"); // Fakes record all calls t.ok(fake.calledOnce, "fake.calledOnce is true"); t.equal( fake.firstArg, undefined, "fake.firstArg is undefined when no arguments passed" ); t.end(); }); ``` ## Prefer fakes over spies and stubs Fakes are alternatives to the older [spies][spies] and [stubs][stubs], and can replace them in all use cases. They are designed to be simpler and easier to use, while avoiding confusion by being [immutable][immutable]. All `fakes` [have the same API][spy-api] as [`spies`][spies]. This includes access to call information through the [spy call API][spy-call-api], such as `firstArg`, `lastArg`, and `callback` properties. ## Using fakes instead of spies ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("Using fakes instead of spies", (t) => { const foo = { bar: () => "baz" }; // wrap existing method without changing its behavior const fake = sinon.replace(foo, "bar", sinon.fake(foo.bar)); // behavior is the same t.equal(fake(), "baz", "fake returns original behavior"); // records information about calls t.equal(fake.callCount, 1, "callCount is tracked"); sinon.restore(); t.end(); }); ``` ## Using fakes instead of stubs ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("Using fakes instead of stubs", (t) => { const foo = { bar: () => "baz" }; // replace method with a fake one const fake = sinon.replace(foo, "bar", sinon.fake.returns("fake value")); // returns fake value t.equal(foo.bar(), "fake value", "fake returns fake value"); // records information about calls t.equal(fake.callCount, 1, "callCount is tracked"); sinon.restore(); t.end(); }); ``` [spies]: /concepts/spies/ [spy-api]: /concepts/spies/api/ [spy-call-api]: /concepts/spy-call/api/ [stubs]: /concepts/stubs/ [replace]: /concepts/sandboxes/api/replace [immutable]: https://en.wikipedia.org/wiki/Immutable_object --- --- url: /concepts/fakes/error-handling.md description: >- Fakes validate their usage and throw errors when used incorrectly. Learn about common fakes errors and how to fix them. --- # Error Handling in Fakes Fakes validate their usage and throw errors when used incorrectly. Understanding these errors helps you use fakes correctly. ## Creating Fakes ### Invalid Argument Type When creating a fake with `sinon.fake(f)`, if you provide an argument, it must be a function. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sinon.fake throws when passed a non-function argument", (t) => { t.throws( () => sinon.fake("not a function"), /Expected f argument to be a Function/, "throws TypeError when argument is not a function" ); t.throws( () => sinon.fake(42), /Expected f argument to be a Function/, "throws TypeError when argument is a number" ); t.throws( () => sinon.fake({}), /Expected f argument to be a Function/, "throws TypeError when argument is an object" ); t.end(); }); ``` ## Yield Methods ### Missing Callback Both `fake.yields()` and `fake.yieldsAsync()` expect the last argument to be a callback function. If you call the fake without a callback, it will throw an error. **For `fake.yields()`:** ```js import tap from "tap"; import * as sinon from "sinon"; import fs from "fs"; tap.test("fake.yields", (t) => { const fake = sinon.fake.yields(null, "file content"); const anotherFake = sinon.fake(); sinon.replace(fs, "readFile", fake); fs.readFile("somefile", (err, data) => { // called with fake values given to yields as arguments t.equal(err, null, "callback receives null error"); t.equal(data, "file content", "callback receives file content"); // since yields is synchronous, anotherFake is not called yet t.notOk(anotherFake.called, "anotherFake not called yet"); sinon.restore(); t.end(); }); anotherFake(); }); tap.test("fake.yields throws when last argument is not a function", (t) => { const fake = sinon.fake.yields("error", "data"); t.throws( () => fake("not a callback"), /Expected last argument to be a function/, "throws TypeError when last argument is not a function" ); t.end(); }); ``` **For `fake.yieldsAsync()`:** ```js import tap from "tap"; import * as sinon from "sinon"; import fs from "fs"; tap.test("fake.yieldsAsync", (t) => { const fake = sinon.fake.yieldsAsync(null, "file content"); const anotherFake = sinon.fake(); sinon.replace(fs, "readFile", fake); fs.readFile("somefile", (err, data) => { // called with fake values given to yields as arguments t.equal(err, null, "callback receives null error"); t.equal(data, "file content", "callback receives file content"); // since yields is asynchronous, anotherFake is called first t.ok(anotherFake.called, "anotherFake was called before callback"); sinon.restore(); t.end(); }); anotherFake(); }); tap.test( "fake.yieldsAsync throws when last argument is not a function", (t) => { const fake = sinon.fake.yieldsAsync("error", "data"); t.throws( () => fake("not a callback"), /Expected last argument to be a function/, "throws TypeError when last argument is not a function" ); t.end(); } ); ``` ## Best Practices 1. **Always provide callbacks** - When using `fake.yields()` or `fake.yieldsAsync()`, ensure the fake is called with a callback as the last argument 2. **Type check arguments** - Only pass functions to `sinon.fake()` when wrapping behavior 3. **Use empty fakes** - If you don't need specific behavior, call `sinon.fake()` without arguments to create an empty fake --- --- url: /concepts/fakes/api/rejects.md description: Creates a fake that returns a rejected `Promise` for the passed value. --- # `fake.rejects(value)` Creates a fake that returns a rejected `Promise` for the passed value. If an `Error` is passed as the `value` argument, then that will be the value of the promise. If any other value is passed, then that will be used for the `message` property of the `Error` returned by the promise. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("fake.rejects", async (t) => { const fake1 = sinon.fake.rejects("not apple pie"); const fake2 = sinon.fake.rejects(new Error("not peach pie")); try { await fake1(); t.fail("fake1 should have rejected"); } catch (error) { t.equal( error.message, "not apple pie", "fake1 rejects with string message" ); } try { await fake2(); t.fail("fake2 should have rejected"); } catch (error) { t.equal(error.message, "not peach pie", "fake2 rejects with Error object"); } t.end(); }); ``` --- --- url: /concepts/fakes/api/resolves.md description: Creates a fake that returns a resolved `Promise` for the passed value. --- # `fake.resolves(value)` Creates a fake that returns a resolved `Promise` for the passed value. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("fake.resolves", async (t) => { const fake = sinon.fake.resolves("Apple pie"); const value = await fake(); t.equal(value, "Apple pie", "fake resolves with provided value"); t.end(); }); ``` --- --- url: /concepts/fakes/api/returns.md description: Creates a fake that returns the provided value. --- # `fake.returns(value);` Creates a fake that returns the provided value. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("fake.returns", (t) => { const value = "apple pie"; const f = sinon.fake.returns(value); t.equal(f(), "apple pie", "fake returns the provided value"); t.end(); }); ``` --- --- url: /concepts/fakes/api/throws.md description: >- Creates a fake, that throws an `Error` with the provided value as the `message` property. --- # `fake.throws(messageOrError)` Creates a fake, that throws an `Error` with the provided value as the `message` property. When an `Error` is passed as the `value` argument, that will be the thrown value. If any other value is passed, that will be used for the `message` property of the thrown `Error`. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("fake.throws", (t) => { // use a string const fake1 = sinon.fake.throws("not apple pie"); // use an Error const fake2 = sinon.fake.throws(new Error("not peach pie")); // Expected to throw an error with message 'not apple pie' t.throws( () => fake1(), { message: "not apple pie" }, "fake1 throws error with string message" ); // Expected to throw an error with message 'not peach pie' t.throws( () => fake2(), { message: "not peach pie" }, "fake2 throws error with Error object" ); t.end(); }); ``` --- --- url: /concepts/fakes/api/yields.md description: >- Makes a fake call the callback with the provided values. The last argument must be a callback function. --- # `fake.yields([value1, ..., valueN]);` `fake.yields` takes some values, and returns a function. This function expects the last argument to be a callback. It invokes the callback with the previously given values. The function returned from `fake.yields` is typically used to mimic a service function that takes a callback as the last argument. In the example below, the [`readFile`][readFile] function of the `fs` module is replaced with a `fake` created by `fake.yields`. When the fake function is called, it calls the last argument it received, which is expected to be a callback, with the values that the `yields` function previously took. ```js import tap from "tap"; import * as sinon from "sinon"; import fs from "fs"; tap.test("fake.yields", (t) => { const fake = sinon.fake.yields(null, "file content"); const anotherFake = sinon.fake(); sinon.replace(fs, "readFile", fake); fs.readFile("somefile", (err, data) => { // called with fake values given to yields as arguments t.equal(err, null, "callback receives null error"); t.equal(data, "file content", "callback receives file content"); // since yields is synchronous, anotherFake is not called yet t.notOk(anotherFake.called, "anotherFake not called yet"); sinon.restore(); t.end(); }); anotherFake(); }); tap.test("fake.yields throws when last argument is not a function", (t) => { const fake = sinon.fake.yields("error", "data"); t.throws( () => fake("not a callback"), /Expected last argument to be a function/, "throws TypeError when last argument is not a function" ); t.end(); }); ``` [readFile]: https://nodejs.org/api/fs.html#fs_fs_readfile_path_options_callback --- --- url: /concepts/fakes/api/yields-async.md description: >- Similar to [`fake.yields`][yields], `fake.yieldsAsync` also returns a function that when invoked, expects its last argument to be a callback, and invokes that callback with the same previously give... --- # `fake.yieldsAsync([value1, ..., valueN]);` Similar to [`fake.yields`][yields], `fake.yieldsAsync` also returns a function that when invoked, expects its last argument to be a callback, and invokes that callback with the same previously given values. However, the returned function invokes that callback asynchronously rather than immediately, i.e. in the next event loop. ```js import tap from "tap"; import * as sinon from "sinon"; import fs from "fs"; tap.test("fake.yieldsAsync", (t) => { const fake = sinon.fake.yieldsAsync(null, "file content"); const anotherFake = sinon.fake(); sinon.replace(fs, "readFile", fake); fs.readFile("somefile", (err, data) => { // called with fake values given to yields as arguments t.equal(err, null, "callback receives null error"); t.equal(data, "file content", "callback receives file content"); // since yields is asynchronous, anotherFake is called first t.ok(anotherFake.called, "anotherFake was called before callback"); sinon.restore(); t.end(); }); anotherFake(); }); tap.test( "fake.yieldsAsync throws when last argument is not a function", (t) => { const fake = sinon.fake.yieldsAsync("error", "data"); t.throws( () => fake("not a callback"), /Expected last argument to be a function/, "throws TypeError when last argument is not a function" ); t.end(); } ); ``` [yields]: ./yields --- --- url: /concepts/mocks.md description: >- Fake methods with pre-programmed behavior and expectations. Fails tests if not used as expected. --- # Mocks ## Introduction ::: warning Consider Using Fakes Instead Mocks are powerful but easy to overuse. Consider using [fakes][fakes] with explicit assertions instead. Fakes provide simpler behavior replacement without coupling tests to implementation details. Reserve mocks for cases where upfront expectations truly clarify test intent. ::: Mocks (and expectations) are fake methods (like [spies][spies]) with pre-programmed behavior (like [stubs][stubs]) as well as **pre-programmed expectations**. A mock will fail your test if it is not used as expected. ## When to use mocks? Mocks should only be used for the *method under test*. In every unit test, there should be one unit under test. If you want to control how your unit is being used and like stating expectations upfront (as opposed to asserting after the fact), use a mock. ## When to **not** use mocks? Mocks come with built-in expectations that may fail your test. Thus, they enforce implementation details. The rule of thumb is: if you wouldn't add an assertion for some specific call, don't mock it. Use a stub instead. In general you should have **no more than one** mock (possibly with several expectations) in a single test. [Expectations][expectations] implement both the [spies][spies] and [stubs][stubs] APIs. ## Mocks vs Stubs vs Fakes **Use mocks when:** * You want to declare expectations upfront (before acting) * You need to verify interactions immediately upon use * The expectation itself clarifies the test's intent **Use [stubs][stubs] when:** * You need call-specific behavior (`onCall()`, `onFirstCall()`) * You need argument-based behavior (`withArgs()`) * You're controlling behavior but don't care about verification **Use [fakes][fakes] when:** * You need simple behavior replacement (recommended for most cases) * You want immutable, predictable test doubles * You prefer explicit assertions over built-in expectations [expectations]: ./api/expectations [fakes]: /concepts/fakes/ [spies]: /concepts/spies/ [stubs]: /concepts/stubs/ --- --- url: /concepts/mocks/api/expects.md description: >- Overrides `obj.method` with an [expectation][expectation] (mock function) and returns it. --- # `mock.expects` Overrides `obj.method` with an [expectation][expectation] (mock function) and returns it. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("mock.expects - creates expectation that throws when not met", (t) => { const obj = { greet: function (name) { return `Hello ${name}`; } }; const mock = sinon.mock(obj); const expectation = mock.expects("greet"); t.throws( () => mock.verify(), /Expected greet\('\[...\]'\) once \(never called\)/, "throws when expectation not met" ); mock.restore(); t.end(); }); tap.test("mock.expects - verify succeeds when expectation met", (t) => { const obj = { greet: function (name) { return `Hello ${name}`; } }; const mock = sinon.mock(obj); const expectation = mock.expects("greet"); obj.greet("Mickey Mouse"); const result = mock.verify(); t.ok(result, "verify returns true after calling expected method"); t.end(); }); ``` [expectation]: ./expectations --- --- url: /concepts/mocks/api/restore.md description: Restores all mocked methods. --- # `mock.restore` Restores all mocked methods. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("mock.restore - restores all mocked methods", (t) => { const obj = { greet: function (name) { return `Hello ${name}`; } }; const mock = sinon.mock(obj); const expectation = mock.expects("greet"); obj.greet("Mickey Mouse"); // mocked methods have a restore method on them t.equal( typeof obj.greet.restore, "function", "mocked method has restore function" ); mock.restore(); // the original greet method has been restored t.equal( typeof obj.greet.restore, "undefined", "restored method no longer has restore function" ); t.end(); }); ``` --- --- url: /concepts/mocks/api/verify.md description: Verifies all expectations on the mock and restores all mocked methods. --- # `mock.verify` Verifies all expectations on the mock and restores all mocked methods. If any expectation is not satisfied, an exception is thrown. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("mock.verify - verifies and restores when expectations met", (t) => { const obj = { greet: function (name) { return `Hello ${name}`; } }; const mock = sinon.mock(obj); const expectation = mock.expects("greet"); obj.greet("Mickey Mouse"); // mocked methods have a restore method on them t.equal( typeof obj.greet.restore, "function", "mocked method has restore function" ); const result = mock.verify(); t.ok(result, "verify returns true when expectations met"); // the original greet method has been restored t.equal( typeof obj.greet.restore, "undefined", "restored method no longer has restore function" ); t.end(); }); ``` ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("mock.verify - throws when expectations not met", (t) => { const obj = { greet: function (name) { return `Hello ${name}`; } }; const mock = sinon.mock(obj); const expectation = mock.expects("greet"); t.throws( () => mock.verify(), /Expected greet\('\[...\]'\) once \(never called\)/, "throws when expectation not satisfied" ); mock.restore(); t.end(); }); ``` --- --- url: /concepts/mocks/api/expectations.md description: >- All the expectation methods return an expectation instance, meaning you can chain them. --- # Expectations All the expectation methods return an expectation instance, meaning you can chain them. Typical usage: ```js import t from "tap"; import sinon from "sinon"; t.test("mock expectations can chain atLeast and atMost", (t) => { // Create an object with a real method const obj = { ajax: function () { return "response"; } }; // Set up expectations const mock = sinon.mock(obj); mock.expects("ajax").atLeast(2).atMost(5); // Call the method within the expected range (3 times) obj.ajax(); obj.ajax(); obj.ajax(); // Verify expectations are met t.doesNotThrow(() => { mock.verify(); }, "should not throw when expectations are met"); // Restore mock.restore(); t.end(); }); ``` ## `var expectation = sinon.expectation.create([methodName]);` Creates an expectation without a mock object, which is essentially an anonymous mock function. Method name is optional and is used in exception messages to make them more readable. ## `var expectation = sinon.mock([methodName]);` The same as the above. ## `expectation.atLeast(number);` Specify the minimum amount of calls expected. ## `expectation.atMost(number);` Specify the maximum amount of calls expected. ## `expectation.never();` Expect the method to never be called. ## `expectation.once();` Expect the method to be called exactly once. ## `expectation.twice();` Expect the method to be called exactly twice. ## `expectation.thrice();` Expect the method to be called exactly thrice. ## `expectation.exactly(number);` Expect the method to be called exactly `number` times. ## `expectation.withArgs(arg1, arg2, ...);` Expect the method to be called with the provided arguments and possibly others. An `expectation` instance only holds onto a single set of arguments specified with `withArgs`. Subsequent calls will overwrite the previously-specified set of arguments (even if they are different), so it is generally not intended that this method be invoked more than once per test case. ## `expectation.withExactArgs(arg1, arg2, ...);` Expect the method to be called with the provided arguments and no others. An `expectation` instance only holds onto a single set of arguments specified with `withExactArgs`. Subsequent calls will overwrite the previously-specified set of arguments (even if they are different), so it is generally not intended that this method be invoked more than once per test case. ## `expectation.on(obj);` Expect the method to be called with `obj` as `this`."} ## `expectation.verify();` Verifies the expectation and throws an exception if it's not met. --- --- url: /concepts/matchers.md description: >- Flexible argument matching for assertions. Make tests more expressive with fuzzy or specific value matching. --- # Matchers ## Introduction Matchers can be passed as arguments to [`spy.calledOn`][spy-called-on], [`spy.calledWith`][spy-called-with], [`spy.returned`][spy-returned] and the corresponding [`sinon.assert`][assert] functions as well as [`spy.withArgs`][spy-with-args]. Matchers allow to be either more fuzzy or more specific about the expected value. ```js import t from "tap"; import sinon from "sinon"; t.test(`matcher allows fuzzy comparisons`, (t) => { const book = { pages: 42, author: "cjno", id: { isbn10: "0596517742", isbn13: "978-0596517748" } }; const f = sinon.fake(); f(book); t.ok(f.calledWith(sinon.match({ author: "cjno" }))); t.ok(f.calledWith(sinon.match.has("pages", 42))); t.ok( f.calledWith( sinon.match.has("id", sinon.match.has("isbn13", "978-0596517748")) ) ); t.end(); }); ``` ```js import t from "tap"; import sinon from "sinon"; t.test(`matcher allows specific comparisons`, (t) => { const f = sinon.fake(); f("apple pie"); t.ok(f.calledWith(sinon.match(sinon.match.string))); t.notOk(f.calledWith(sinon.match(sinon.match.number))); t.end(); }); ``` [assert]: /concepts/assertions/ [spy-called-on]: /concepts/spies/api/called-on [spy-called-with]: /concepts/spies/api/called-with [spy-returned]: /concepts/spies/api/returned [spy-with-args]: /concepts/spies/api/with-args --- --- url: /concepts/matchers/api/any.md description: Matches anything. --- # `sinon.match.any` Matches anything. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sinon.match.any", (t) => { const fake = sinon.fake(); // Matches string values fake("hello"); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.any); }, "should accept string"); // Matches number values fake(42); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.any); }, "should accept number"); // Matches object values fake({ name: "Alice" }); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.any); }, "should accept object"); // Matches null fake(null); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.any); }, "should accept null"); // Matches undefined fake(undefined); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.any); }, "should accept undefined"); t.end(); }); ``` --- --- url: /concepts/matchers/api/array.md description: Requires the value to be an `Array`. --- # `sinon.match.array` Requires the value to be an `Array`. ## `sinon.match.array.deepEquals(arr)` Requires an `Array` to be deep equal another one. ## `sinon.match.array.startsWith(arr)` Requires an `Array` to start with the same values as another one. ## `sinon.match.array.endsWith(arr)` Requires an `Array` to end with the same values as another one. ## `sinon.match.array.contains(arr)` Requires an `Array` to contain each one of the values the given array has. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sinon.match.array", (t) => { const fake = sinon.fake(); fake([1, 2, 3]); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.array); }, "should accept array"); fake([]); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.array); }, "should accept empty array"); fake.resetHistory(); fake({ length: 3 }); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.array), /expected fake to be called with match/, "should reject object" ); fake.resetHistory(); fake("array"); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.array), /expected fake to be called with match/, "should reject string" ); t.end(); }); ``` --- --- url: /concepts/matchers/api/bool.md description: Requires the value to be a `Boolean` --- # `sinon.match.bool` Requires the value to be a `Boolean` ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sinon.match.bool", (t) => { const fake = sinon.fake(); fake(true); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.bool); }, "should accept true"); fake(false); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.bool); }, "should accept false"); fake.resetHistory(); fake(1); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.bool), /expected fake to be called with match/, "should reject number" ); fake.resetHistory(); fake("true"); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.bool), /expected fake to be called with match/, "should reject string" ); t.end(); }); ``` --- --- url: /concepts/matchers/api/defined.md description: Requires the value to be defined. --- # `sinon.match.defined` Requires the value to be defined. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sinon.match.defined", (t) => { const fake = sinon.fake(); fake("hello"); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.defined); }, "should accept string"); fake(0); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.defined); }, "should accept zero"); fake(false); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.defined); }, "should accept false"); fake.resetHistory(); fake(null); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.defined), /expected fake to be called with match/, "should reject null" ); fake.resetHistory(); fake(undefined); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.defined), /expected fake to be called with match/, "should reject undefined" ); t.end(); }); ``` --- --- url: /concepts/matchers/api/every.md description: >- Requires **every** element of an `Array`, `Set` or `Map`, or alternatively **every** value of an `Object` to match the given `matcher`. --- # `sinon.match.every(matcher)` Requires **every** element of an `Array`, `Set` or `Map`, or alternatively **every** value of an `Object` to match the given `matcher`. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sinon.match.every", (t) => { const fake = sinon.fake(); fake([2, 4, 6, 8]); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.every(sinon.match.number)); }, "should accept array where every element is a number"); fake(new Set(["apple", "banana", "cherry"])); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.every(sinon.match.string)); }, "should accept Set where every element is a string"); fake({ a: 1, b: 2, c: 3 }); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.every(sinon.match.number)); }, "should accept object where every value is a number"); fake.resetHistory(); fake([2, 4, "six", 8]); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.every(sinon.match.number)), /expected fake to be called with match/, "should reject array with non-number element" ); fake.resetHistory(); fake([]); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.every(sinon.match.number)); }, "should accept empty array (vacuous truth)"); t.end(); }); ``` --- --- url: /concepts/matchers/api/has-nested.md description: >- Requires the value to define the given `propertyPath`. Dot (`prop.prop`) and bracket (`prop[0]`) notations are supported as in [Lodash.get](https://lodash.com/docs/4.4.2#get). --- # `sinon.match.hasNested(propertyPath[, expectation])` Requires the value to define the given `propertyPath`. Dot (`prop.prop`) and bracket (`prop[0]`) notations are supported as in [Lodash.get](https://lodash.com/docs/4.4.2#get). The propertyPath might be inherited via the prototype chain. If the optional expectation is given, the value at the propertyPath is deeply compared with the expectation. The expectation can be another matcher. ```js import t from "tap"; import sinon from "sinon"; t.test( "sinon.match.hasNested matches nested properties with array notation", (t) => { const matcher = sinon.match.hasNested("a[0].b.c"); const actual = { a: [{ b: { c: 3 } }] }; // Verify the matcher matches the nested structure t.ok( matcher.test(actual), "should match nested property with array notation" ); // Verify it doesn't match when property is missing const noMatch = { a: [{ b: {} }] }; t.notOk( matcher.test(noMatch), "should not match when nested property is missing" ); t.end(); } ); ``` ```js import t from "tap"; import sinon from "sinon"; t.test( "sinon.match.hasNested matches nested properties with dot notation", (t) => { const matcher = sinon.match.hasNested("a.b.c"); const actual = { a: { b: { c: 3 } } }; // Verify the matcher matches the nested structure t.ok( matcher.test(actual), "should match nested property with dot notation" ); // Verify it doesn't match when property is missing const noMatch = { a: { b: {} } }; t.notOk( matcher.test(noMatch), "should not match when nested property is missing" ); t.end(); } ); ``` --- --- url: /concepts/matchers/api/has-own.md description: >- Same as `sinon.match.has` but the property must be defined by the value itself. Inherited properties are ignored. --- # `sinon.match.hasOwn(property[, expectation])` Same as `sinon.match.has` but the property must be defined by the value itself. Inherited properties are ignored. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sinon.match.hasOwn", (t) => { const fake = sinon.fake(); fake({ name: "Alice" }); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.hasOwn("name")); }, "should accept object with own property"); fake({ name: "Alice", age: 30 }); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.hasOwn("name", "Alice")); }, "should accept matching own property value"); const obj = Object.create({ inherited: true }); fake(obj); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.hasOwn("inherited")), /expected fake to be called with match/, "should reject inherited property" ); fake.resetHistory(); fake({ name: "Alice" }); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.hasOwn("age")), /expected fake to be called with match/, "should reject missing property" ); t.end(); }); ``` --- --- url: /concepts/matchers/api/has.md description: Requires the value to define the given `property`. --- # `sinon.match.has(property[, expectation])` Requires the value to define the given `property`. The property might be inherited via the prototype chain. If the optional expectation is given, the value of the property is deeply compared with the expectation. The expectation can be another matcher. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sinon.match.has", (t) => { const fake = sinon.fake(); fake({ name: "Alice" }); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.has("name")); }, "should accept object with property"); fake({ name: "Alice", age: 30 }); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.has("name", "Alice")); }, "should accept matching property value"); const obj = Object.create({ inherited: true }); fake(obj); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.has("inherited")); }, "should accept inherited property"); fake.resetHistory(); fake({ name: "Alice" }); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.has("age")), /expected fake to be called with match/, "should reject missing property" ); fake.resetHistory(); fake({ name: "Alice" }); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.has("name", "Bob")), /expected fake to be called with match/, "should reject wrong property value" ); t.end(); }); ``` --- --- url: /concepts/matchers/api/in.md description: Requires the value to be in the `array`. --- # `sinon.match.in(array)` Requires the value to be in the `array`. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sinon.match.in", (t) => { const fake = sinon.fake(); fake("apple"); t.doesNotThrow(() => { sinon.assert.calledWithMatch( fake, sinon.match.in(["apple", "banana", "cherry"]) ); }, "should accept value in array"); fake(42); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.in([1, 42, 100])); }, "should use strict equality"); fake.resetHistory(); fake("orange"); t.throws( () => sinon.assert.calledWithMatch( fake, sinon.match.in(["apple", "banana", "cherry"]) ), /expected fake to be called with match/, "should reject value not in array" ); fake.resetHistory(); fake("42"); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.in([1, 42, 100])), /expected fake to be called with match/, "should reject with loose equality" ); t.end(); }); ``` --- --- url: /concepts/matchers/api/instance-of.md description: Requires the value to be an instance of the given `type`. --- # `sinon.match.instanceOf(type)` Requires the value to be an instance of the given `type`. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sinon.match.instanceOf", (t) => { const fake = sinon.fake(); fake(new Date()); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.instanceOf(Date)); }, "should accept Date instance"); class Person { constructor(name) { this.name = name; } } fake(new Person("Alice")); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.instanceOf(Person)); }, "should accept custom class instance"); fake.resetHistory(); fake(new Date()); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.instanceOf(RegExp)), /expected fake to be called with match/, "should reject different type" ); fake.resetHistory(); fake("hello"); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.instanceOf(String)), /expected fake to be called with match/, "should reject primitive string" ); t.end(); }); ``` --- --- url: /concepts/matchers/api/map.md description: Requires the value to be a `Map`. --- # `sinon.match.map` Requires the value to be a `Map`. ## `sinon.match.map.deepEquals(map)` Requires a `Map` to be deep equal another one. ## `sinon.match.map.contains(map)` Requires a `Map` to contain each one of the items the given map has. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sinon.match.map", (t) => { const fake = sinon.fake(); const map = new Map([["key", "value"]]); fake(map); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.map); }, "should accept Map"); fake(new Map()); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.map); }, "should accept empty Map"); fake.resetHistory(); fake({ key: "value" }); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.map), /expected fake to be called with match/, "should reject object" ); fake.resetHistory(); fake([["key", "value"]]); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.map), /expected fake to be called with match/, "should reject array" ); t.end(); }); ``` --- --- url: /concepts/matchers/api/match.md description: Requires the value to be == to the given number. --- # `sinon.match(number);` Requires the value to be == to the given number. # `sinon.match(string);` Requires the value to be a string and have the expectation as a substring. # `sinon.match(regexp);` Requires the value to be a string and match the given regular expression. # `sinon.match(object);` Requires the value to be not `null` or `undefined` and have at least the same properties as `expectation`. This supports nested matchers. # `sinon.match(function)` See [`custom matchers`](../custom-matchers). ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sinon.match", (t) => { const fake = sinon.fake(); // match(number) fake(42); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match(42)); }, "should accept equal number"); fake.resetHistory(); fake(42); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match(43)), /expected fake to be called with match/, "should reject different number" ); // match(string) - substring fake.resetHistory(); fake("hello world"); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match("world")); }, "should accept string containing substring"); fake.resetHistory(); fake("hello world"); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match("goodbye")), /expected fake to be called with match/, "should reject string without substring" ); // match(regexp) fake.resetHistory(); fake("test123"); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match(/test\d+/)); }, "should accept string matching regex"); fake.resetHistory(); fake("hello"); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match(/\d+/)), /expected fake to be called with match/, "should reject string not matching regex" ); // match(object) - partial match fake.resetHistory(); fake({ name: "Alice", age: 30, city: "NYC" }); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match({ name: "Alice" })); }, "should accept object with matching properties"); // match(object) - nested matchers fake.resetHistory(); fake({ user: { name: "Alice", age: 30 } }); t.doesNotThrow(() => { sinon.assert.calledWithMatch( fake, sinon.match({ user: sinon.match({ name: "Alice" }) }) ); }, "should support nested matchers"); fake.resetHistory(); fake({ name: "Alice" }); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match({ name: "Bob" })), /expected fake to be called with match/, "should reject object with different property values" ); // match(function) - custom matcher fake.resetHistory(); fake(42); t.doesNotThrow(() => { sinon.assert.calledWithMatch( fake, sinon.match((value) => value > 40) ); }, "should use custom function"); fake.resetHistory(); fake(42); t.throws( () => sinon.assert.calledWithMatch( fake, sinon.match((value) => value > 50) ), /expected fake to be called with match/, "should reject when custom function returns false" ); t.end(); }); ``` --- --- url: /concepts/matchers/api/number.md description: Requires the value to be a `Number`. --- # `sinon.match.number` Requires the value to be a `Number`. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sinon.match.number", (t) => { const fake = sinon.fake(); fake(42); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.number); }, "should accept number"); fake(0); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.number); }, "should accept zero"); fake(-123); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.number); }, "should accept negative number"); fake.resetHistory(); fake("42"); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.number), /expected fake to be called with match/, "should reject string" ); t.end(); }); ``` --- --- url: /concepts/matchers/api/object.md description: Requires the value to be an `Object`. --- # `sinon.match.object` Requires the value to be an `Object`. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sinon.match.object", (t) => { const fake = sinon.fake(); fake({ name: "Alice" }); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.object); }, "should accept object"); fake({}); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.object); }, "should accept empty object"); fake.resetHistory(); fake(null); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.object), /expected fake to be called with match/, "should reject null" ); fake.resetHistory(); fake("hello"); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.object), /expected fake to be called with match/, "should reject string" ); t.end(); }); ``` --- --- url: /concepts/matchers/api/regexp.md description: Requires the value to be a regular expression. --- # `sinon.match.regexp` Requires the value to be a regular expression. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sinon.match.regexp", (t) => { const fake = sinon.fake(); fake(/test/); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.regexp); }, "should accept RegExp"); fake(/test/gi); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.regexp); }, "should accept RegExp with flags"); fake.resetHistory(); fake("/test/"); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.regexp), /expected fake to be called with match/, "should reject string" ); fake.resetHistory(); fake({ pattern: "test" }); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.regexp), /expected fake to be called with match/, "should reject object" ); t.end(); }); ``` --- --- url: /concepts/matchers/api/same.md description: Requires the value to strictly equal `ref`. --- # `sinon.match.same(ref)` Requires the value to strictly equal `ref`. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sinon.match.same", (t) => { const fake = sinon.fake(); const obj = { name: "Alice" }; fake(obj); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.same(obj)); }, "should accept same object reference"); fake(42); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.same(42)); }, "should accept same primitive value"); fake.resetHistory(); fake({ name: "Alice" }); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.same({ name: "Alice" })), /expected fake to be called with match/, "should reject different object" ); fake.resetHistory(); fake(42); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.same(43)), /expected fake to be called with match/, "should reject different value" ); t.end(); }); ``` --- --- url: /concepts/matchers/api/set.md description: Requires the value to be a `Set`. --- # `sinon.match.set` Requires the value to be a `Set`. ## `sinon.match.set.deepEquals(set)` Requires a `Set` to be deep equal another one. ## `sinon.match.set.contains(set)` Requires a `Set` to contain each one of the items the given set has. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sinon.match.set", (t) => { const fake = sinon.fake(); const set = new Set([1, 2, 3]); fake(set); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.set); }, "should accept Set"); fake(new Set()); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.set); }, "should accept empty Set"); fake.resetHistory(); fake([1, 2, 3]); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.set), /expected fake to be called with match/, "should reject array" ); fake.resetHistory(); fake({ values: [1, 2, 3] }); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.set), /expected fake to be called with match/, "should reject object" ); t.end(); }); ``` --- --- url: /concepts/matchers/api/some.md description: >- Requires **any** element of an `Array`, `Set` or `Map`, or alternatively **any** value of an `Object` to match the given `matcher`. --- # `sinon.match.some(matcher)` Requires **any** element of an `Array`, `Set` or `Map`, or alternatively **any** value of an `Object` to match the given `matcher`. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sinon.match.some", (t) => { const fake = sinon.fake(); fake([1, "two", 3, "four"]); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.some(sinon.match.string)); }, "should accept array with at least one string"); fake(new Set([1, 2, "three"])); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.some(sinon.match.string)); }, "should accept Set with at least one string"); fake({ a: 1, b: "two", c: 3 }); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.some(sinon.match.string)); }, "should accept object with at least one string value"); fake.resetHistory(); fake([1, 2, 3, 4]); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.some(sinon.match.string)), /expected fake to be called with match/, "should reject array with no strings" ); fake.resetHistory(); fake([]); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.some(sinon.match.string)), /expected fake to be called with match/, "should reject empty array" ); t.end(); }); ``` --- --- url: /concepts/matchers/api/string.md description: Requires the value to be a `String`. --- # `sinon.match.string` Requires the value to be a `String`. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sinon.match.string", (t) => { const fake = sinon.fake(); // Matches string values fake("hello"); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.string); }, "should accept string"); // Matches empty string fake(""); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.string); }, "should accept empty string"); // Rejects number fake.resetHistory(); fake(42); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.string), /expected fake to be called with match/, "should reject number" ); // Rejects object fake.resetHistory(); fake({ name: "Alice" }); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.string), /expected fake to be called with match/, "should reject object" ); t.end(); }); ``` --- --- url: /concepts/matchers/api/symbol.md description: Requires the value to be a `Symbol`. --- # `sinon.match.symbol` Requires the value to be a `Symbol`. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sinon.match.symbol", (t) => { const fake = sinon.fake(); fake(Symbol("test")); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.symbol); }, "should accept Symbol"); fake(Symbol.iterator); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.symbol); }, "should accept well-known Symbol"); fake.resetHistory(); fake("Symbol(test)"); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.symbol), /expected fake to be called with match/, "should reject string" ); fake.resetHistory(); fake({ type: "symbol" }); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.symbol), /expected fake to be called with match/, "should reject object" ); t.end(); }); ``` --- --- url: /concepts/matchers/api/truthy.md description: Requires the value to be truthy. --- # `sinon.match.truthy` Requires the value to be truthy. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sinon.match.truthy", (t) => { const fake = sinon.fake(); fake(true); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.truthy); }, "should accept true"); fake("hello"); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.truthy); }, "should accept non-empty string"); fake(42); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.truthy); }, "should accept non-zero number"); fake.resetHistory(); fake(false); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.truthy), /expected fake to be called with match/, "should reject false" ); fake.resetHistory(); fake(0); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.truthy), /expected fake to be called with match/, "should reject zero" ); fake.resetHistory(); fake(""); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.truthy), /expected fake to be called with match/, "should reject empty string" ); t.end(); }); ``` --- --- url: /concepts/matchers/api/type-of.md description: >- Requires the value to be of the given type, where `type` can be one of `"undefined"`, `"null"`, `"boolean"`, `"number"`, `"string"`, `"object"`, `"function"`, --- # `sinon.match.typeOf(type)` Requires the value to be of the given type, where `type` can be one of `"undefined"`, `"null"`, `"boolean"`, `"number"`, `"string"`, `"object"`, `"function"`, `"array"`, `"regexp"`, `"date"` or `"symbol"`. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sinon.match.typeOf", (t) => { const fake = sinon.fake(); fake("hello"); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.typeOf("string")); }, "should accept string type"); fake(42); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.typeOf("number")); }, "should accept number type"); fake(true); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.typeOf("boolean")); }, "should accept boolean type"); fake(() => {}); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.typeOf("function")); }, "should accept function type"); fake({}); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.typeOf("object")); }, "should accept object type"); fake.resetHistory(); fake(42); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.typeOf("string")), /expected fake to be called with match/, "should reject wrong type" ); t.end(); }); ``` --- --- url: /concepts/assertions.md description: >- Built-in assertions that mirror spy/stub behavior. Provides detailed error messages when assertions fail. --- # Assertions Sinon.JS ships with a set of assertions that mirror most behavior verification methods and properties on [fakes][fakes], [spies][spies] and [stubs][stubs]. The advantage of using the assertions is that failed expectations on [fakes][fakes], [spies][spies] and [stubs][stubs] can be expressed directly as assertion failures with detailed and helpful error messages. ## Examples ### Without `sinon.assert` ```js import t from "tap"; import sinon from "sinon"; t.test("without sinon.assert, failures lack helpful context", (t) => { const f = sinon.fake(); // Using generic assertion without sinon.assert provides less helpful errors // This would fail with a generic "expected false to be true" message t.notOk(f.calledOnce, "fake should not be called yet"); // Call the fake f(); // Now it should be true t.ok(f.calledOnce, "fake should be called once"); t.end(); }); ``` ### With `sinon.assert` ```js import t from "tap"; import sinon from "sinon"; t.test("with sinon.assert, failures provide detailed error messages", (t) => { const msg = "Apple Pie"; const f = sinon.fake(); // Verify that sinon.assert throws when expectation is not met t.throws( () => sinon.assert.calledOnce(f), /expected fake to be called once but was called 0 times/i, "should throw with detailed message when fake not called" ); // Call the fake f(msg); // No error should be thrown when assertions pass t.doesNotThrow( () => sinon.assert.calledOnce(f), "should not throw when fake called once" ); t.doesNotThrow( () => sinon.assert.calledWith(f, msg), "should not throw when fake called with correct argument" ); t.end(); }); ``` ## Integrations * [jest-sinon](https://www.npmjs.com/package/jest-sinon) * [referee-sinon](https://github.com/sinonjs/referee-sinon?tab=readme-ov-file#referee-sinon) - from the makers of Sinon 🙂 * [sinon-chai](https://github.com/chaijs/sinon-chai#readme) To make sure assertions integrate nicely with your assertion framework, you should customize [`sinon.assert.fail`][fail] and look into [`sinon.assert.expose`][expose] and [`sinon.assert.pass`][pass]. [expose]: ./api/expose [fail]: ./api/fail [pass]: ./api/pass [fakes]: /concepts/fakes/ [spies]: /concepts/spies/ [stubs]: /concepts/stubs/ --- --- url: /concepts/assertions/api/always-called-on.md description: >- Passes, when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] has only been called with `object` as its `this` value. --- # `assert.alwaysCalledOn(spyOrSpyCall, object);` Passes, when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] has only been called with `object` as its `this` value. ```js import * as sinon from "sinon"; const spy = sinon.spy(); const object = { name: "Apple Pie" }; const differentObject = { name: "Cherry Pie" }; // Has not been called yet, so the error message is a bit short :) sinon.assert.alwaysCalledOn(spy, object); // => Uncaught: Error [AssertError]: expected spy to be called with { name: 'Apple Pie' } as this but was called with spy.call(object); // Generates no error sinon.assert.alwaysCalledOn(spy, object); spy.call(differentObject); sinon.assert.alwaysCalledOn(spy, object); // => Uncaught: Error [AssertError]: expected spy to always be called with { name: 'Apple Pie' } as this but was called with { name: 'Apple Pie' }, { name: 'Cherry Pie' } ``` See [`Function.prototype.call()`][proto-call]. ## Example using test framework ```js import tap from "tap"; import * as sinon from "sinon"; tap.test( "assert.alwaysCalledOn - passes when all calls have correct this context", (t) => { const obj = { name: "test object" }; const fake = sinon.fake(); fake.call(obj); fake.call(obj); t.doesNotThrow(() => { sinon.assert.alwaysCalledOn(fake, obj); }, "assertion should pass"); t.end(); } ); tap.test( "assert.alwaysCalledOn - fails when one call has different this context", (t) => { const obj1 = { name: "obj1" }; const obj2 = { name: "obj2" }; const fake = sinon.fake(); fake.call(obj1); fake.call(obj2); t.throws( () => sinon.assert.alwaysCalledOn(fake, obj1), /expected fake to always be called with/, "assertion should fail with different this context" ); t.end(); } ); ``` [proto-call]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call [fake]: /concepts/fakes/ [spy]: /concepts/spies/ [stub]: /concepts/stubs/ --- --- url: /concepts/assertions/api/always-called-with.md description: >- Passes when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] has **always** been called with the provided arguments. --- # `assert.alwaysCalledWith(spy, arg1, arg2, ...);` Passes when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] has **always** been called with the provided arguments. ```js import * as sinon from "sinon"; const fake = sinon.fake(); sinon.assert.alwaysCalledWith(fake, "apple pie"); // => Uncaught Error [AssertError]: expected fake to always be called with arguments fake("apple pie", "cherry pie"); // Generates no error sinon.assert.alwaysCalledWith(fake, "apple pie"); sinon.assert.alwaysCalledWith(fake, "cherry pie"); fake("lemon meringue pie"); sinon.assert.alwaysCalledWith(fake, "apple pie"); // => Uncaught Error [AssertError]: expected fake to always be called with arguments // => Call 1: // => '"apple pie"' // => '"cherry pie"' // => Call 2: // => '"lemon meringue pie"' '"apple pie"' ``` If you want to assert that the `fake` was always called with exactly the specified arguments, use [`sinon.assert.alwaysCalledWithExactly`][alwaysCalledWithExactly] ## Example using test framework ```js import tap from "tap"; import * as sinon from "sinon"; tap.test( "assert.alwaysCalledWith - passes when all calls have arguments", (t) => { const fake = sinon.fake(); fake("apple"); fake("apple", "pie"); fake("apple", "pie", "ice cream"); t.doesNotThrow(() => { sinon.assert.alwaysCalledWith(fake, "apple"); }, "assertion should pass when all calls include argument"); t.end(); } ); tap.test( "assert.alwaysCalledWith - fails when one call lacks arguments", (t) => { const fake = sinon.fake(); fake("apple"); fake("banana"); t.throws( () => sinon.assert.alwaysCalledWith(fake, "apple"), /expected fake to always be called with arguments/, "assertion should fail when not all calls include argument" ); t.end(); } ); ``` [alwaysCalledWithExactly]: ./always-called-with-exactly [fake]: /concepts/fakes/ [spy]: /concepts/spies/ [stub]: /concepts/stubs/ --- --- url: /concepts/assertions/api/always-called-with-exactly.md description: >- Passes when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] has **always, only** been called with the provided arguments. --- # `assert.alwaysCalledWithExactly(spy, arg1, arg2, ...);` Passes when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] has **always, only** been called with the provided arguments. ```js import * as sinon from "sinon"; const fake = sinon.fake(); sinon.assert.alwaysCalledWithExactly(fake, "apple pie"); // => Uncaught Error [AssertError]: expected fake to always be called with arguments fake("apple pie"); // Generates no error sinon.assert.alwaysCalledWithExactly(fake, "apple pie"); fake("apple pie", "lemon meringue pie"); sinon.assert.alwaysCalledWithExactly(fake, "apple pie"); // => Uncaught Error [AssertError]: expected fake to always be called with exact // => arguments // => Call 1: // => '"apple pie"' // => Call 2: // => '"apple pie"' // => '"lemon meringue pie"' ``` ## Example using test framework ```js import tap from "tap"; import * as sinon from "sinon"; tap.test( "assert.alwaysCalledWithExactly - passes when all calls have exact arguments", (t) => { const fake = sinon.fake(); fake("apple", "pie"); fake("apple", "pie"); t.doesNotThrow(() => { sinon.assert.alwaysCalledWithExactly(fake, "apple", "pie"); }, "assertion should pass"); t.end(); } ); tap.test( "assert.alwaysCalledWithExactly - fails when one call has different arguments", (t) => { const fake = sinon.fake(); fake("apple", "pie"); fake("apple", "tart"); t.throws( () => sinon.assert.alwaysCalledWithExactly(fake, "apple", "pie"), /expected fake to always be called with exact arguments/, "assertion should fail with different arguments" ); t.end(); } ); ``` [fake]: /concepts/fakes/ [spy]: /concepts/spies/ [stub]: /concepts/stubs/ --- --- url: /concepts/assertions/api/always-called-with-match.md description: >- Passes, when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] was always called with matching arguments. --- # `assert.alwaysCalledWithMatch(spy, arg1, arg2, ...)` Passes, when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] was always called with matching arguments. This behaves the same way as [`sinon.assert.alwaysCalledWith(spy, sinon.match(arg1), sinon.match(arg2), ...)`][always-called-with]. ```js import * as sinon from "sinon"; const fake = sinon.fake(); const applePieExpectation = { name: "apple pie" }; fake({ name: "apple pie", price: 123 }); // Matches, generates no error sinon.assert.alwaysCalledWithMatch(fake, applePieExpectation); fake({ name: "cherry pie", price: 123 }); sinon.assert.alwaysCalledWithMatch(fake, applePieExpectation); //=> Uncaught Error [AssertError]: expected fake to always be called with match //=> Call 1: //=> { name: 'apple pie', price: 123 } { name: 'apple pie' } //=> Call 2: //=> { name: 'cherry pie', price: 123 } { name: 'apple pie' } ``` ## Example using test framework ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("assert.alwaysCalledWithMatch - passes when all calls match", (t) => { const fake = sinon.fake(); fake({ name: "Alice", age: 30 }); fake({ name: "Bob", age: 40 }); t.doesNotThrow(() => { sinon.assert.alwaysCalledWithMatch(fake, { age: sinon.match.number }); }, "assertion should pass when all calls match"); t.end(); }); tap.test( "assert.alwaysCalledWithMatch - fails when one call doesn't match", (t) => { const fake = sinon.fake(); fake({ name: "Alice" }); fake({ name: "Bob", age: 40 }); t.throws( () => sinon.assert.alwaysCalledWithMatch(fake, { age: sinon.match.number }), /expected fake to always be called with match/, "assertion should fail when not all calls match" ); t.end(); } ); ``` [always-called-with]: ./always-called-with [fake]: /concepts/fakes/ [spy]: /concepts/spies/ [stub]: /concepts/stubs/ --- --- url: /concepts/assertions/api/always-threw.md description: 'Like above, only required for all calls to the spy.' --- ## `assert.alwaysThrew(spy, exception);` Like above, only required for all calls to the spy. Passes, when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] always threw the given exception. The exception can be a `String` denoting its type, or an actual object. When only one argument is provided, the assertion passes if `spy` always threw any exception. ```js import * as sinon from "sinon"; const f1 = sinon.fake(); f1("apple pie"); sinon.assert.alwaysThrew(f1, "TypeError"); // => Uncaught Error [AssertError]: fake did not throw exception const f2 = sinon.fake.throws(new TypeError("not an apple pie")); try { f2("apple pie"); } catch (err) { // not used } // Generates no error sinon.assert.alwaysThrew(f2, "TypeError"); ``` ## Example using test framework ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("assert.alwaysThrew - passes when all calls threw", (t) => { const fake = sinon.fake.throws(new Error("boom")); try { fake(); } catch (e) {} try { fake(); } catch (e) {} t.doesNotThrow(() => { sinon.assert.alwaysThrew(fake); }, "assertion should pass when all calls threw"); t.end(); }); tap.test("assert.alwaysThrew - fails when one call didn't throw", (t) => { const fake = sinon.fake(); fake(); t.throws( () => sinon.assert.alwaysThrew(fake), /fake did not always throw exception/, "assertion should fail when not all calls threw" ); t.end(); }); ``` [fake]: /concepts/fakes/ [spy]: /concepts/spies/ [stub]: /concepts/stubs/ --- --- url: /concepts/assertions/api/call-count.md description: >- Passes if the [`fake`][fake], [`spy`][spy] or [`stub`][stub] was called exactly `num` times. --- # `assert.callCount(spy, num);` Passes if the [`fake`][fake], [`spy`][spy] or [`stub`][stub] was called exactly `num` times. ```js import * as sinon from "sinon"; const spy = sinon.spy(); sinon.assert.callCount(spy, 1); // => Uncaught Error [AssertError]: expected spy to be called once but was called 0 times spy(); // Generates no exception sinon.assert.callCount(spy, 1); ``` ## Example using test framework ```js import tap from "tap"; import * as sinon from "sinon"; tap.test( "assert.callCount - passes when spy was called exact number of times", (t) => { const spy = sinon.spy(); spy(); t.doesNotThrow(() => { sinon.assert.callCount(spy, 1); }, "assertion should pass"); t.end(); } ); tap.test("assert.callCount - fails when spy was not called", (t) => { const spy = sinon.spy(); t.throws( () => sinon.assert.callCount(spy, 1), /expected spy to be called once but was called 0 times/, "assertion should fail with descriptive message" ); t.end(); }); tap.test( "assert.callCount - fails when spy was called wrong number of times", (t) => { const spy = sinon.spy(); spy(); spy(); spy(); t.throws( () => sinon.assert.callCount(spy, 5), /expected spy to be called 5 times but was called thrice/, "assertion should fail with descriptive message" ); t.end(); } ); ``` [fake]: /concepts/fakes/ [spy]: /concepts/spies/ [stub]: /concepts/stubs/ --- --- url: /concepts/assertions/api/called.md description: >- Passes, when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] was called at least once. --- # `assert.called(spy);` Passes, when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] was called at least once. ```js import * as sinon from "sinon"; const spy = sinon.spy(); sinon.assert.called(spy); // => Error [AssertError]: expected spy to have been called at least once but was never called spy(); // Generates no exception sinon.assert.called(spy); ``` ## Example using test framework ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("assert.called - passes when spy was called", (t) => { const spy = sinon.spy(); spy(); t.doesNotThrow(() => { sinon.assert.called(spy); }, "assertion should pass"); t.end(); }); tap.test("assert.called - fails when spy was not called", (t) => { const spy = sinon.spy(); t.throws( () => sinon.assert.called(spy), /expected spy to have been called at least once but was never called/, "assertion should fail with descriptive message" ); t.end(); }); ``` [fake]: /concepts/fakes/ [spy]: /concepts/spies/ [stub]: /concepts/stubs/ --- --- url: /concepts/assertions/api/called-on.md description: >- Passes, when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] was called at least once with `object` as `this`. --- # `assert.calledOn(spyOrSpyCall, object);` Passes, when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] was called at least once with `object` as `this`. It's possible to assert on a dedicated spy call: `sinon.assert.calledOn(call, arg1, arg2, ...);`. ```js import * as sinon from "sinon"; const spy = sinon.spy(); const object = { name: "Apple Pie" }; // Has not been called yet, so the error message is a bit short :) sinon.assert.calledOn(spy, object); // => Uncaught: Error [AssertError]: expected spy to be called with { name: 'Apple Pie' } as this but was called with spy.call(object); // Generates no error sinon.assert.calledOn(spy, object); // Generates no error sinon.assert.calledOn(spy.firstCall, object); ``` ## Asserting on a `spyCall` ```js import * as sinon from "sinon"; const spy = sinon.spy(); const object = { name: "Apple Pie" }; spy.call(object); // get a spyCall instance const call = spy.firstCall; // Generates no error sinon.assert.calledOn(call, object); ``` See [`Function.prototype.call()`][proto-call]. ## Example using test framework ```js import tap from "tap"; import * as sinon from "sinon"; tap.test( "assert.calledOn - passes when spy was called with correct this context", (t) => { const obj = { name: "test object" }; const fake = sinon.fake(); fake.call(obj); t.doesNotThrow(() => { sinon.assert.calledOn(fake, obj); }, "assertion should pass"); t.end(); } ); tap.test("assert.calledOn - fails when spy was not called", (t) => { const obj = { name: "test object" }; const fake = sinon.fake(); t.throws( () => sinon.assert.calledOn(fake, obj), /expected fake to be called with/, "assertion should fail when not called" ); t.end(); }); tap.test( "assert.calledOn - fails when spy was called with different this context", (t) => { const obj1 = { name: "obj1" }; const obj2 = { name: "obj2" }; const fake = sinon.fake(); fake.call(obj1); t.throws( () => sinon.assert.calledOn(fake, obj2), /expected fake to be called with/, "assertion should fail with wrong this context" ); t.end(); } ); ``` [proto-call]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call [fake]: /concepts/fakes/ [spy]: /concepts/spies/ [stub]: /concepts/stubs/ --- --- url: /concepts/assertions/api/called-once.md description: >- Passes if the [`fake`][fake], [`spy`][spy] or [`stub`][stub] was called exactly once. --- # `assert.calledOnce(spy);` Passes if the [`fake`][fake], [`spy`][spy] or [`stub`][stub] was called exactly once. ```js import * as sinon from "sinon"; const spy = sinon.spy(); sinon.assert.calledOnce(spy); // => Error [AssertError]: expected spy to be called once but was called 0 times spy(); // Generates no exception sinon.assert.calledOnce(spy); spy(); sinon.assert.calledOnce(spy); // => Error [AssertError]: expected spy to be called once but was called twice ``` ## Example using test framework ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("assert.calledOnce - passes when spy was called once", (t) => { const spy = sinon.spy(); spy(); t.doesNotThrow(() => { sinon.assert.calledOnce(spy); }, "assertion should pass"); t.end(); }); tap.test("assert.calledOnce - fails when spy was not called", (t) => { const spy = sinon.spy(); t.throws( () => sinon.assert.calledOnce(spy), /expected spy to be called once but was called 0 times/, "assertion should fail when not called" ); t.end(); }); tap.test( "assert.calledOnce - fails when spy was called multiple times", (t) => { const spy = sinon.spy(); spy(); spy(); t.throws( () => sinon.assert.calledOnce(spy), /expected spy to be called once but was called twice/, "assertion should fail when called twice" ); t.end(); } ); ``` [fake]: /concepts/fakes/ [spy]: /concepts/spies/ [stub]: /concepts/stubs/ --- --- url: /concepts/assertions/api/called-once-with-exactly.md description: >- Passes, when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] was called exactly once, with exactly the provided arguments. --- # `assert.calledOnceWithExactly(spyOrSpyCall, arg1, arg2, ...);` Passes, when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] was called exactly once, with exactly the provided arguments. It's possible to assert on a dedicated [spyCall][spy-call]: `sinon.assert.calledOnceWithExactly(call, arg1, arg2, ...);`. ```js import * as sinon from "sinon"; const fake = sinon.fake(); sinon.assert.calledOnceWithExactly(fake, "apple pie"); // => Uncaught Error [AssertError]: expected fake to be called with exact arguments fake("apple pie"); // Generates no error sinon.assert.calledOnceWithExactly(fake, "apple pie"); fake("apple pie"); sinon.assert.calledOnceWithExactly(fake, "apple pie"); // => Uncaught Error [AssertError]: expected fake to be called once and with exact arguments // => Call 1: // => '"apple pie"' // => Call 2: // => '"apple pie"' ``` ## Asserting on a `spyCall` ```js import * as sinon from "sinon"; const fake = sinon.fake(); fake("apple pie"); // get a spyCall instance const call = fake.firstCall; // Generates no error sinon.assert.calledOnceWithExactly(fake, "apple pie"); ``` ## Example using test framework ```js import tap from "tap"; import * as sinon from "sinon"; tap.test( "assert.calledOnceWithExactly - passes when called once with exact arguments", (t) => { const fake = sinon.fake(); fake("apple", "pie"); t.doesNotThrow(() => { sinon.assert.calledOnceWithExactly(fake, "apple", "pie"); }, "assertion should pass"); t.end(); } ); tap.test("assert.calledOnceWithExactly - fails when called twice", (t) => { const fake = sinon.fake(); fake("apple", "pie"); fake("apple", "pie"); t.throws( () => sinon.assert.calledOnceWithExactly(fake, "apple", "pie"), /expected fake to be called once/, "assertion should fail when called more than once" ); t.end(); }); tap.test("assert.calledOnceWithExactly - fails with wrong arguments", (t) => { const fake = sinon.fake(); fake("apple"); t.throws( () => sinon.assert.calledOnceWithExactly(fake, "apple", "pie"), /expected fake to be called once/, "assertion should fail with wrong arguments" ); t.end(); }); ``` [spy-call]: /concepts/spy-call/ [fake]: /concepts/fakes/ [spy]: /concepts/spies/ [stub]: /concepts/stubs/ --- --- url: /concepts/assertions/api/called-once-with-match.md description: >- Passes, when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] was called exactly once with matching arguments. --- # `assert.calledOnceWithMatch(spy, arg1, arg2, ...)` Passes, when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] was called exactly once with matching arguments. ```js import * as sinon from "sinon"; const fake = sinon.fake(); const applePieExpectation = { name: "apple pie" }; fake({ name: "apple pie", price: 123 }); // Matches, generates no error sinon.assert.calledOnceWithMatch(fake, applePieExpectation); fake({ name: "apple pie", price: 123 }); sinon.assert.calledOnceWithMatch(fake, applePieExpectation); //=> Uncaught Error [AssertError]: expected fake to be called once and with match //=> Call 1: //=> { name: 'apple pie', price: 123 } { name: 'apple pie' } //=> Call 2: //=> { name: 'apple pie', price: 123 } { name: 'apple pie' } ``` ## Example using test framework ```js import tap from "tap"; import * as sinon from "sinon"; tap.test( "assert.calledOnceWithMatch - passes when called once with matching arguments", (t) => { const fake = sinon.fake(); fake({ name: "Alice", age: 30 }); t.doesNotThrow(() => { sinon.assert.calledOnceWithMatch(fake, { name: "Alice" }); }, "assertion should pass"); t.end(); } ); tap.test("assert.calledOnceWithMatch - fails when called twice", (t) => { const fake = sinon.fake(); fake({ name: "Alice" }); fake({ name: "Alice" }); t.throws( () => sinon.assert.calledOnceWithMatch(fake, { name: "Alice" }), /expected fake to be called once/, "assertion should fail when called more than once" ); t.end(); }); tap.test( "assert.calledOnceWithMatch - fails with non-matching arguments", (t) => { const fake = sinon.fake(); fake({ name: "Bob" }); t.throws( () => sinon.assert.calledOnceWithMatch(fake, { name: "Alice" }), /expected fake to be called once/, "assertion should fail with non-matching arguments" ); t.end(); } ); ``` [fake]: /concepts/fakes/ [spy]: /concepts/spies/ [stub]: /concepts/stubs/ --- --- url: /concepts/assertions/api/called-twice.md description: >- Passes if the [`fake`][fake], [`spy`][spy] or [`stub`][stub] was called exactly twice. --- # `assert.calledTwice(spy);` Passes if the [`fake`][fake], [`spy`][spy] or [`stub`][stub] was called exactly twice. ```js import * as sinon from "sinon"; const spy = sinon.spy(); sinon.assert.calledTwice(spy); // => Uncaught Error [AssertError]: expected spy to be called twice but was called 0 times spy(); spy(); // Generates no exception sinon.assert.calledTwice(spy); spy(); sinon.assert.calledTwice(spy); // => Uncaught Error [AssertError]: expected spy to be called twice but was called thrice ``` ## Example using test framework ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("assert.calledTwice - passes when spy was called twice", (t) => { const spy = sinon.spy(); spy(); spy(); t.doesNotThrow(() => { sinon.assert.calledTwice(spy); }, "assertion should pass"); t.end(); }); tap.test("assert.calledTwice - fails when spy was called once", (t) => { const spy = sinon.spy(); spy(); t.throws( () => sinon.assert.calledTwice(spy), /expected spy to be called twice but was called once/, "assertion should fail with descriptive message" ); t.end(); }); ``` [fake]: /concepts/fakes/ [spy]: /concepts/spies/ [stub]: /concepts/stubs/ --- --- url: /concepts/assertions/api/called-thrice.md description: >- Passes if the [`fake`][fake], [`spy`][spy] or [`stub`][stub] was called exactly three times. --- # `assert.calledThrice(spy);` Passes if the [`fake`][fake], [`spy`][spy] or [`stub`][stub] was called exactly three times. ```js import * as sinon from "sinon"; const spy = sinon.spy(); sinon.assert.calledThrice(spy); // => Uncaught Error [AssertError]: expected spy to be called thrice but was called 0 times spy(); spy(); spy(); // Generates no exception sinon.assert.calledThrice(spy); spy(); sinon.assert.calledThrice(spy); // => Uncaught Error [AssertError]: expected spy to be called thrice but was called 4 times ``` ## Example using test framework ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("assert.calledThrice - passes when spy was called thrice", (t) => { const spy = sinon.spy(); spy(); spy(); spy(); t.doesNotThrow(() => { sinon.assert.calledThrice(spy); }, "assertion should pass"); t.end(); }); tap.test("assert.calledThrice - fails when spy was called twice", (t) => { const spy = sinon.spy(); spy(); spy(); t.throws( () => sinon.assert.calledThrice(spy), /expected spy to be called thrice but was called twice/, "assertion should fail with descriptive message" ); t.end(); }); ``` [fake]: /concepts/fakes/ [spy]: /concepts/spies/ [stub]: /concepts/stubs/ --- --- url: /concepts/assertions/api/called-with.md description: >- Passes, when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] was called with the provided arguments. --- # `assert.calledWith(spyOrSpyCall, arg1, arg2, ...);` Passes, when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] was called with the provided arguments. It's possible to assert on a dedicated [spyCall][spy-call]: `sinon.assert.calledWith(call, arg1, arg2, ...);`. ```js import * as sinon from "sinon"; const fake = sinon.fake(); sinon.assert.calledWith(fake, "apple pie"); // => Uncaught Error [AssertError]: expected fake to be called with arguments fake("apple pie"); // Generates no error sinon.assert.calledWith(fake, "apple pie"); sinon.assert.calledWith(fake, "lemon meringue pie"); // => Uncaught Error [AssertError]: expected fake to be called with arguments // => '"apple pie"' '"lemon meringue pie"' ``` ## Asserting on a `spyCall` ```js import * as sinon from "sinon"; const fake = sinon.fake(); fake("apple pie"); // get a spyCall instance const call = fake.firstCall; // Generates no error sinon.assert.calledWith(call, "apple pie"); ``` ## Example using test framework ```js import tap from "tap"; import * as sinon from "sinon"; tap.test( "assert.calledWith - passes when spy was called with arguments", (t) => { const fake = sinon.fake(); fake("apple pie"); t.doesNotThrow(() => { sinon.assert.calledWith(fake, "apple pie"); }, "assertion should pass"); t.end(); } ); tap.test("assert.calledWith - fails when spy was not called", (t) => { const fake = sinon.fake(); t.throws( () => sinon.assert.calledWith(fake, "apple pie"), /expected fake to be called with arguments/, "assertion should fail when not called" ); t.end(); }); tap.test( "assert.calledWith - fails when spy was called with different arguments", (t) => { const fake = sinon.fake(); fake("apple pie"); t.throws( () => sinon.assert.calledWith(fake, "lemon meringue pie"), /expected fake to be called with arguments/, "assertion should fail with wrong arguments" ); t.end(); } ); tap.test("assert.calledWith - works with spyCall", (t) => { const fake = sinon.fake(); fake("apple pie"); const call = fake.firstCall; t.doesNotThrow(() => { sinon.assert.calledWith(call, "apple pie"); }, "assertion should work on spyCall"); t.end(); }); ``` [spy-call]: /concepts/spy-call/ [fake]: /concepts/fakes/ [spy]: /concepts/spies/ [stub]: /concepts/stubs/ --- --- url: /concepts/assertions/api/called-with-exactly.md description: >- Passes, when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] was called with exactly the provided arguments. --- # `assert.calledWithExactly(spyOrSpyCall, arg1, arg2, ...);` Passes, when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] was called with exactly the provided arguments. It's possible to assert on a dedicated [spyCall][spy-call]: `sinon.assert.calledWithExactly(call, arg1, arg2, ...);`. ```js import * as sinon from "sinon"; const fake = sinon.fake(); sinon.assert.calledWithExactly(fake, "apple pie"); // => Uncaught Error [AssertError]: expected fake to be called with exact arguments fake("apple pie"); // Generates no error sinon.assert.calledWithExactly(fake, "apple pie"); sinon.assert.calledWithExactly(fake, "lemon meringue pie"); // => Uncaught Error [AssertError]: expected fake to be called with exact arguments // => '"apple pie"' '"lemon meringue pie"' // reset the history of everything sinon.resetHistory(); sinon.assert.calledWithExactly(fake, "apple pie"); // => Uncaught Error [AssertError]: expected fake to be called with exact arguments ``` ## Asserting on a `spyCall` ```js import * as sinon from "sinon"; const fake = sinon.fake(); fake("apple pie"); // get a spyCall instance const call = fake.firstCall; // Generates no error sinon.assert.calledWithExactly(call, "apple pie"); ``` ## Example using test framework ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("assert.calledWithExactly - passes with exact arguments", (t) => { const fake = sinon.fake(); fake("apple", "pie"); t.doesNotThrow(() => { sinon.assert.calledWithExactly(fake, "apple", "pie"); }, "assertion should pass with exact arguments"); t.end(); }); tap.test("assert.calledWithExactly - fails with extra arguments", (t) => { const fake = sinon.fake(); fake("apple", "pie", "ice cream"); t.throws( () => sinon.assert.calledWithExactly(fake, "apple", "pie"), /expected fake to be called with exact arguments/, "assertion should fail when extra arguments present" ); t.end(); }); tap.test("assert.calledWithExactly - fails with missing arguments", (t) => { const fake = sinon.fake(); fake("apple"); t.throws( () => sinon.assert.calledWithExactly(fake, "apple", "pie"), /expected fake to be called with exact arguments/, "assertion should fail when arguments missing" ); t.end(); }); ``` [spy-call]: /concepts/spy-call/ [fake]: /concepts/fakes/ [spy]: /concepts/spies/ [stub]: /concepts/stubs/ --- --- url: /concepts/assertions/api/called-with-match.md description: >- Passes, when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] was called with matching arguments. --- # `assert.calledWithMatch(spyOrSpyCall, arg1, arg2, ...)` Passes, when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] was called with matching arguments. This behaves the same way as [`sinon.assert.calledWith(spy, sinon.match(arg1), sinon.match(arg2), ...)`][calledWith]. It's possible to assert on a dedicated [spyCall][spy-call]: `sinon.assert.calledWithMatch(spy.secondCall, arg1, arg2, ...);`. ```js import * as sinon from "sinon"; const fake = sinon.fake(); const applePieExpectation = { name: "apple pie" }; const cherryPieExpectation = { name: "cherry pie" }; fake({ name: "apple pie", price: 123 }); // Matches, generates no error sinon.assert.calledWithMatch(fake, applePieExpectation); // Does not match sinon.assert.calledWithMatch(fake, cherryPieExpectation); // => Uncaught Error [AssertError]: expected fake to be called with match // => { name: 'apple pie', price: 123 } { name: 'cherry pie' } ``` ## Asserting on a `spyCall` ```js import * as sinon from "sinon"; const fake = sinon.fake(); const applePieExpectation = { name: "apple pie" }; const cherryPieExpectation = { name: "cherry pie" }; fake({ name: "apple pie", price: 123 }); // get a spyCall instance const call = fake.firstCall; // Matches, generates no error sinon.assert.calledWithMatch(call, applePieExpectation); ``` ## Example using test framework ```js import tap from "tap"; import * as sinon from "sinon"; tap.test( "assert.calledWithMatch - passes when spy was called with matching arguments", (t) => { const fake = sinon.fake(); fake({ name: "Alice", age: 30 }); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, { name: "Alice" }); }, "assertion should pass with partial match"); t.end(); } ); tap.test("assert.calledWithMatch - passes with matcher", (t) => { const fake = sinon.fake(); fake("apple pie"); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.string); }, "assertion should pass with matcher"); t.end(); }); tap.test("assert.calledWithMatch - fails when spy was not called", (t) => { const fake = sinon.fake(); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.string), /expected fake to be called with match/, "assertion should fail when not called" ); t.end(); }); tap.test("assert.calledWithMatch - fails when arguments don't match", (t) => { const fake = sinon.fake(); fake({ name: "Bob" }); t.throws( () => sinon.assert.calledWithMatch(fake, { name: "Alice" }), /expected fake to be called with match/, "assertion should fail with non-matching arguments" ); t.end(); }); ``` [spy-call]: /concepts/spy-call/ [calledWith]: ./called-with [fake]: /concepts/fakes/ [spy]: /concepts/spies/ [stub]: /concepts/stubs/ --- --- url: /concepts/assertions/api/called-with-new.md description: >- Passes, when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] was called with the `new` operator. --- # `assert.calledWithNew(spyOrSpyCall);` Passes, when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] was called with the `new` operator. It's possible to assert on a dedicated [spyCall][spy-call]: `sinon.assert.calledWithNew(call);`. ```js import * as sinon from "sinon"; const fake = sinon.fake(); sinon.assert.calledWithNew(fake); // => Uncaught Error [AssertError]: expected fake to be called with new fake(); sinon.assert.calledWithNew(fake); // => Uncaught Error [AssertError]: expected fake to be called with new new fake(); // Generates no error sinon.assert.calledWithNew(fake); ``` ## Asserting on a `spyCall` ```js import * as sinon from "sinon"; const fake = sinon.fake(); new fake(); // get a spyCall instance const call = fake.firstCall; // Generates no error sinon.assert.calledWithNew(call); ``` ## Example using test framework ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("assert.calledWithNew - passes when spy was called with new", (t) => { const Ctor = sinon.fake(); new Ctor(); t.doesNotThrow(() => { sinon.assert.calledWithNew(Ctor); }, "assertion should pass"); t.end(); }); tap.test("assert.calledWithNew - fails when spy was not called", (t) => { const Ctor = sinon.fake(); t.throws( () => sinon.assert.calledWithNew(Ctor), /expected fake to be called with new/, "assertion should fail when not called" ); t.end(); }); tap.test( "assert.calledWithNew - fails when spy was called without new", (t) => { const Ctor = sinon.fake(); Ctor(); t.throws( () => sinon.assert.calledWithNew(Ctor), /expected fake to be called with new/, "assertion should fail when called without new" ); t.end(); } ); ``` [spy-call]: /concepts/spy-call/ [fake]: /concepts/fakes/ [spy]: /concepts/spies/ [stub]: /concepts/stubs/ --- --- url: /concepts/assertions/api/call-order.md description: >- Passes, when provided [`fakes`][fakes], [`spies`][spies] or [`stubs`][stubs] are called in the specified order. --- # `assert.callOrder(spy1, spy2, ...);` Passes, when provided [`fakes`][fakes], [`spies`][spies] or [`stubs`][stubs] are called in the specified order. ```js import * as sinon from "sinon"; const fake = sinon.fake(); const spy = sinon.spy(); const stub = sinon.stub(); fake(); spy(); stub(); // not the called order sinon.assert.callOrder(spy, stub, fake); // => Uncaught: Error [AssertError]: expected spy, stub, fake to be called in order but were called as fake, spy, stub // the called order - generates no error sinon.assert.callOrder(fake, spy, stub); ``` ## Example using test framework ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("assert.callOrder - passes when spies called in order", (t) => { const spy1 = sinon.spy(); const spy2 = sinon.spy(); const spy3 = sinon.spy(); spy1(); spy2(); spy3(); t.doesNotThrow(() => { sinon.assert.callOrder(spy1, spy2, spy3); }, "assertion should pass when called in order"); t.end(); }); tap.test("assert.callOrder - fails when spies called out of order", (t) => { const spy1 = sinon.spy(); const spy2 = sinon.spy(); const spy3 = sinon.spy(); spy3(); spy1(); spy2(); t.throws( () => sinon.assert.callOrder(spy1, spy2, spy3), /expected.*to be called in order/, "assertion should fail when called out of order" ); t.end(); }); tap.test("assert.callOrder - works with subset of spies", (t) => { const spy1 = sinon.spy(); const spy2 = sinon.spy(); spy1(); spy2(); t.doesNotThrow(() => { sinon.assert.callOrder(spy1, spy2); }, "assertion should pass when spies called in order"); t.end(); }); ``` [fakes]: /concepts/fakes/ [spies]: /concepts/spies/ [stubs]: /concepts/stubs/ --- --- url: /concepts/assertions/api/expose.md description: >- Exposes assertions into another object, to allow for integration with a test framework. --- # `assert.expose(target, options);` Exposes assertions into another object, to allow for integration with a test framework. For example, [sinon-chai][0] exposes Sinon's assertions on its own object. ```js import t from "tap"; import sinon from "sinon"; t.test("assert.expose integrates assertions into another object", (t) => { // Create a target object to expose assertions onto const myAssert = {}; // Expose sinon assertions with blank prefix sinon.assert.expose(myAssert, { prefix: "" }); // Verify assertions are exposed without prefix t.ok(myAssert.called, "should have 'called' assertion"); t.ok(myAssert.calledOnce, "should have 'calledOnce' assertion"); t.ok(myAssert.calledWith, "should have 'calledWith' assertion"); // Verify the exposed assertions work const fake = sinon.fake(); fake("arg"); t.doesNotThrow(() => myAssert.called(fake), "exposed assertion should work"); t.end(); }); ``` This will give you `spy.should.have.been.called` and so on. See [sinon-chai documentation][0] for usage examples. The method accepts an optional options object with two options: [0]: https://github.com/chaijs/sinon-chai?tab=readme-ov-file#assertions --- --- url: /concepts/assertions/api/fail.md description: This convenience method can cause a test library to fail a test. --- # `assert.fail(message)` This convenience method can cause a test library to fail a test. Every assertion fails by calling this method. ```js import * as sinon from "sinon"; const msg = "Apple Pie"; sinon.assert.fail(msg); // => Uncaught Error [AssertError]: Apple Pie ``` By default it throws an error of type `sinon.assert.failException`. If the test framework looks for assertion errors by checking for a specific exception, you can override the kind of exception thrown. If that does not fit with your testing framework of choice, override the `fail` method to do the right thing. ## Example using test framework ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("assert.fail - throws with message", (t) => { t.throws( () => sinon.assert.fail("custom failure message"), /custom failure message/, "fail should throw with provided message" ); t.end(); }); tap.test("assert.fail - throws AssertError", (t) => { try { sinon.assert.fail("test"); t.fail("should have thrown"); } catch (e) { t.equal(e.name, "AssertError", "error should be AssertError"); } t.end(); }); ``` --- --- url: /concepts/assertions/api/match.md description: >- Uses [`sinon.match`][matchers] to test if the arguments can be considered a match. --- # `assert.match(actual, expectation);` Uses [`sinon.match`][matchers] to test if the arguments can be considered a match. ```js import * as sinon from "sinon"; const expected = { x: 1 }; const actual = { x: 1, y: 2 }; // Generates no errors sinon.assert.match(actual, expected); // Doesn't match sinon.assert.match({ y: 3 }, expected); // => Uncaught Error [AssertError]: expected value to match // => expected = { x: 1 } // => actual = { y: 3 } ``` ## Example using test framework ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("assert.match - passes when value matches", (t) => { t.doesNotThrow(() => { sinon.assert.match("apple pie", "apple pie"); }, "assertion should pass with exact match"); t.end(); }); tap.test("assert.match - passes with partial object match", (t) => { t.doesNotThrow(() => { sinon.assert.match({ name: "Alice", age: 30 }, { name: "Alice" }); }, "assertion should pass with partial match"); t.end(); }); tap.test("assert.match - passes with matcher", (t) => { t.doesNotThrow(() => { sinon.assert.match("apple pie", sinon.match.string); }, "assertion should pass with matcher"); t.end(); }); tap.test("assert.match - fails when value doesn't match", (t) => { t.throws( () => sinon.assert.match("apple", "banana"), /expected value to match/, "assertion should fail with non-matching value" ); t.end(); }); tap.test("assert.match - fails with non-matching object", (t) => { t.throws( () => sinon.assert.match({ name: "Alice" }, { name: "Bob" }), /expected value to match/, "assertion should fail with non-matching object" ); t.end(); }); ``` [matchers]: /concepts/matchers/ --- --- url: /concepts/assertions/api/never-called-with.md description: >- Passes when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] has never been called with the provided arguments. --- # `assert.neverCalledWith(spy, arg1, arg2, ...);` Passes when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] has never been called with the provided arguments. ```js import * as sinon from "sinon"; const fake = sinon.fake(); // Generates no error sinon.assert.neverCalledWith(fake, "apple pie"); fake("apple pie"); sinon.assert.neverCalledWith(fake, "apple pie"); // => Uncaught: Error [AssertError]: expected fake to never be called with arguments 'apple pie' // Generates no error sinon.assert.neverCalledWith(fake, "lemon meringue pie"); ``` ## Example using test framework ```js import tap from "tap"; import * as sinon from "sinon"; tap.test( "assert.neverCalledWith - passes when not called with arguments", (t) => { const fake = sinon.fake(); fake("apple"); t.doesNotThrow(() => { sinon.assert.neverCalledWith(fake, "banana"); }, "assertion should pass"); t.end(); } ); tap.test("assert.neverCalledWith - fails when called with arguments", (t) => { const fake = sinon.fake(); fake("apple"); t.throws( () => sinon.assert.neverCalledWith(fake, "apple"), /expected fake to never be called with arguments/, "assertion should fail when called with specified arguments" ); t.end(); }); ``` [fake]: /concepts/fakes/ [spy]: /concepts/spies/ [stub]: /concepts/stubs/ --- --- url: /concepts/assertions/api/never-called-with-match.md description: >- Passes, when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] was never called with matching arguments. --- # `assert.neverCalledWithMatch(spy, arg1, arg2, ...)` Passes, when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] was never called with matching arguments. This behaves the same way as [`sinon.assert.neverCalledWith(spy, sinon.match(arg1), sinon.match(arg2), ...)`][never-called-with]. ```js import * as sinon from "sinon"; const fake = sinon.fake(); const applePieExpectation = { name: "apple pie" }; fake({ name: "cherry pie", price: 123 }); // No match, no error :) sinon.assert.neverCalledWithMatch(fake, applePieExpectation); fake({ name: "apple pie", price: 123 }); sinon.assert.neverCalledWithMatch(fake, applePieExpectation); //=> Uncaught: Error [AssertError]: expected fake to never be called with match { name: 'apple pie' } //=> fake({ name: 'cherry pie', price: 123 }) at REPL10:1:1 //=> fake({ name: 'apple pie', price: 123 }) at REPL16:1:1 ``` ## Example using test framework ```js import tap from "tap"; import * as sinon from "sinon"; tap.test( "assert.neverCalledWithMatch - passes when not called with matching arguments", (t) => { const fake = sinon.fake(); fake({ name: "Bob" }); t.doesNotThrow(() => { sinon.assert.neverCalledWithMatch(fake, { name: "Alice" }); }, "assertion should pass"); t.end(); } ); tap.test( "assert.neverCalledWithMatch - passes with matcher that doesn't match", (t) => { const fake = sinon.fake(); fake(123); t.doesNotThrow(() => { sinon.assert.neverCalledWithMatch(fake, sinon.match.string); }, "assertion should pass when matcher doesn't match"); t.end(); } ); tap.test( "assert.neverCalledWithMatch - fails when called with matching arguments", (t) => { const fake = sinon.fake(); fake({ name: "Alice", age: 30 }); t.throws( () => sinon.assert.neverCalledWithMatch(fake, { name: "Alice" }), /expected fake to never be called with match/, "assertion should fail when called with matching arguments" ); t.end(); } ); ``` [never-called-with]: ./never-called-with [fake]: /concepts/fakes/ [spy]: /concepts/spies/ [stub]: /concepts/stubs/ --- --- url: /concepts/assertions/api/not-called.md description: >- Passes, when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] has not been called. --- # `assert.notCalled(spy);` Passes, when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] has not been called. ```js import * as sinon from "sinon"; const spy = sinon.spy(); // No exception! sinon.assert.notCalled(spy); spy(); sinon.assert.notCalled(spy); // => Uncaught Error [AssertError]: expected spy to not have been called but was called once ``` ## Example using test framework ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("assert.notCalled - passes when spy was not called", (t) => { const spy = sinon.spy(); t.doesNotThrow(() => { sinon.assert.notCalled(spy); }, "assertion should pass"); t.end(); }); tap.test("assert.notCalled - fails when spy was called", (t) => { const spy = sinon.spy(); spy(); t.throws( () => sinon.assert.notCalled(spy), /expected spy to not have been called but was called once/, "assertion should fail with descriptive message" ); t.end(); }); ``` [fake]: /concepts/fakes/ [spy]: /concepts/spies/ [stub]: /concepts/stubs/ --- --- url: /concepts/assertions/api/pass.md description: Called every time `assertion` passes. --- # `assert.pass(assertion);` Called every time `assertion` passes. Default implementation does nothing. This method can be overridden by test runners to keep track of how many assertions have passed. Unless you're writing an integration for Sinon into a test runner, you won't need this. See: https://github.com/sinonjs/sinon/issues/2657 for idea on replacing it ## Example using test framework ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("assert.pass - always succeeds", (t) => { t.doesNotThrow(() => { sinon.assert.pass("test assertion"); }, "pass should not throw"); t.end(); }); tap.test("assert.pass - can be called without arguments", (t) => { t.doesNotThrow(() => { sinon.assert.pass(); }, "pass should work without arguments"); t.end(); }); ``` --- --- url: /concepts/assertions/api/threw.md description: >- Passes, when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] threw the given exception. --- # `assert.threw(spyOrSpyCall, exception);` Passes, when the [`fake`][fake], [`spy`][spy] or [`stub`][stub] threw the given exception. The exception can be a `String` denoting its type, or an actual object. When only one argument is provided, the assertion passes if `spy` ever threw any exception. It's possible to assert on a dedicated [spyCall][spy-call]: `sinon.assert.threw(spy.thirdCall, exception);`. ```js import * as sinon from "sinon"; const f1 = sinon.fake(); f1("apple pie"); sinon.assert.threw(f1, "TypeError"); // => Uncaught Error [AssertError]: fake did not throw exception const f2 = sinon.fake.throws(new TypeError("not an apple pie")); try { f2("apple pie"); } catch (err) { // not used } // Generates no error sinon.assert.threw(f2, "TypeError"); ``` ## Asserting on a `spyCall` ```js import * as sinon from "sinon"; const f1 = sinon.fake(); f1("apple pie"); // get a spyCall instance const c1 = f1.firstCall; sinon.assert.threw(c1, "TypeError"); // => Uncaught Error [AssertError]: fake('apple pie') at REPL4:1:1 did not throw exception const f2 = sinon.fake.throws(new TypeError("not an apple pie")); try { f2("apple pie"); } catch (err) { // not used } const c2 = f2.firstCall; // Generates no error sinon.assert.threw(c2, "TypeError"); ``` ## Example using test framework ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("assert.threw - passes when spy threw", (t) => { const fake = sinon.fake.throws(new Error("boom")); try { fake(); } catch (e) {} t.doesNotThrow(() => { sinon.assert.threw(fake); }, "assertion should pass when spy threw"); t.end(); }); tap.test("assert.threw - passes when spy threw specific exception", (t) => { const error = new TypeError("type error"); const fake = sinon.fake.throws(error); try { fake(); } catch (e) {} t.doesNotThrow(() => { sinon.assert.threw(fake, "TypeError"); }, "assertion should pass with exception type"); t.end(); }); tap.test("assert.threw - fails when spy didn't throw", (t) => { const fake = sinon.fake(); fake(); t.throws( () => sinon.assert.threw(fake), /fake did not throw exception/, "assertion should fail when spy didn't throw" ); t.end(); }); ``` [spy-call]: /concepts/spy-call/ [fake]: /concepts/fakes/ [spy]: /concepts/spies/ [stub]: /concepts/stubs/ --- --- url: /concepts/sandboxes.md description: >- Manage multiple fakes, spies, and stubs with automatic cleanup. Simplifies test teardown by grouping related fakes. --- # Sandboxes ## Introduction Sandboxes remove the need to keep track of every fake created, which greatly simplifies cleanup. ## Default sandbox The `sinon` object itself is a sandbox, known as the *default sandbox*. It has all the methods and properties as the [sandbox API][sandbox-api]. ## Using the default sandbox This is the recommended way to use sandboxes. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sandbox - using the default sandbox", (t) => { const myObject = { hello: "world" }; // using the stub method on the default sandbox sinon.stub(myObject, "hello").value("Sinon"); t.equal(myObject.hello, "Sinon", "property stubbed to Sinon"); sinon.restore(); t.equal(myObject.hello, "world", "property restored to world"); t.end(); }); ``` ## Using a custom sandbox Unless you have an advanced setup or need a divergent configuration, you probably want to only use the default sandbox. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sandbox - using a custom sandbox", (t) => { const sandbox = sinon.createSandbox(); const myObject = { hello: "world" }; // using the stub method on the sandbox sandbox.stub(myObject, "hello").value("Banana"); t.equal(myObject.hello, "Banana", "property stubbed to Banana"); sandbox.restore(); t.equal(myObject.hello, "world", "property restored to world"); t.end(); }); ``` [sandbox-api]: ./api/ --- --- url: /concepts/sandboxes/api/create-stub-instance.md description: 'Works exactly like the utility [`sinon.createStubInstance`](../../utils).' --- # `sandbox.createStubInstance();` Works exactly like the utility [`sinon.createStubInstance`](../../utils). --- --- url: /concepts/sandboxes/api/mock.md description: 'Works exactly like [`sinon.mock`](/concepts/mocks/)' --- ## `sandbox.mock();` Works exactly like [`sinon.mock`](/concepts/mocks/) --- --- url: /concepts/sandboxes/api/replace.md description: Replaces `property` on `object` with `replacement` argument. --- # `sandbox.replace(object, property, replacement);` Replaces `property` on `object` with `replacement` argument. Attempting to replace an already replaced value causes an exception. Returns the `replacement`. `replacement` can be any value, including [`fakes`](/concepts/fakes/), [`spies`](/concepts/spies/) and [`stubs`](/concepts/stubs/). This method only works on non-accessor properties, for replacing accessors use [`sandbox.replaceGetter()`](./replace-getter) and [`sandbox.replaceSetter()`](./replace-setter). ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sandbox.replace - basic usage", (t) => { // The sinon root object is a default sandbox var myObject = { myMethod: function () { return "apple pie"; } }; sinon.replace(myObject, "myMethod", function () { return "strawberry"; }); const result = myObject.myMethod(); t.equal(result, "strawberry", "method replaced with strawberry"); sinon.restore(); t.end(); }); ``` --- --- url: /concepts/sandboxes/api/replace-getter.md description: >- Replaces getter for `property` on `object` with `replacement` argument. Attempting to replace an already replaced getter causes an exception. --- # `sandbox.replaceGetter();` Replaces getter for `property` on `object` with `replacement` argument. Attempting to replace an already replaced getter causes an exception. `replacement` must be a `Function`, and can be instances of [`fake`](/concepts/fakes/), [`spy`](/concepts/spies/) and [`stub`](/concepts/stubs/). ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sandbox.replaceGetter - basic usage", (t) => { // The sinon root object is a default sandbox const object = { get myProperty() { return "apple pie"; } }; sinon.replaceGetter(object, "myProperty", function () { return "strawberry"; }); t.equal(object.myProperty, "strawberry", "getter replaced"); sinon.restore(); t.end(); }); ``` --- --- url: /concepts/sandboxes/api/replace-setter.md description: >- Replaces setter for `property` on `object` with `replacement` argument. Attempting to replace an already replaced setter causes an exception. --- # `sandbox.replaceSetter();` Replaces setter for `property` on `object` with `replacement` argument. Attempting to replace an already replaced setter causes an exception. `replacement` must be a `Function`, and can be instances of [`fake`](/concepts/fakes/), [`spy`](/concepts/spies/) and [`stub`](/concepts/stubs/). ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sandbox.replaceSetter - basic usage", (t) => { // The sinon root object is a default sandbox const object = { set myProperty(value) { this.prop = value; } }; sinon.replaceSetter(object, "myProperty", function (value) { this.prop = "strawberry " + value; }); object.myProperty = "pie"; t.equal(object.prop, "strawberry pie", "setter replaced and called"); sinon.restore(); t.end(); }); ``` --- --- url: /concepts/sandboxes/api/reset.md description: >- Resets the mutable behavior in [`stubs`](/concepts/stubs/) as well as the history of all [`fakes`](/concepts/fakes/), [`spies`](/concepts/spies/) and [`stubs`](/concepts/stubs/) created using the sandbox. --- # `sandbox.reset();` Resets the mutable behavior in [`stubs`](/concepts/stubs/) as well as the history of all [`fakes`](/concepts/fakes/), [`spies`](/concepts/spies/) and [`stubs`](/concepts/stubs/) created using the sandbox. In terms of resetting history, this is equivalent to calling [`sandbox.resetHistory`](./reset-history). ## Example: resetting mutable behavior of stubs ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sandbox.reset - resetting mutable behavior of stubs", (t) => { // The sinon root object is a default sandbox const stub = sinon.stub(); // Observe the default behavior t.equal(typeof stub(), "undefined", "stub returns undefined by default"); // Set some behavior for this stub stub.returns("Apple pie"); // Try it out t.equal(stub(), "Apple pie", "stub returns Apple pie"); // Reset behavior and history of everything created using the default // sandbox `sinon` sinon.reset(); // Observe the default behavior t.equal(typeof stub(), "undefined", "stub returns undefined after reset"); t.end(); }); ``` ## Example: `sinon.reset` does not change immutable behavior in fakes ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sandbox.reset - does not change immutable behavior in fakes", (t) => { // The sinon root object is a default sandbox const fake = sinon.fake.returns("Strawberry pie"); // Observe the set (immutable) behavior t.equal(fake(), "Strawberry pie", "fake returns Strawberry pie"); sinon.reset(); // Observe the set (immutable) behavior still exists t.equal( fake(), "Strawberry pie", "fake still returns Strawberry pie after reset" ); t.end(); }); ``` --- --- url: /concepts/sandboxes/api/reset-behavior.md description: Resets the behavior of all stubs created through the sandbox. --- # `sandbox.resetBehavior();` Resets the behavior of all stubs created through the sandbox. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sandbox.resetBehavior - resets behavior of all stubs", (t) => { // The sinon root object is a default sandbox const stub = sinon.stub(); stub.returns(54); t.equal(stub(), 54, "stub returns 54"); sinon.resetBehavior(); t.equal(typeof stub(), "undefined", "stub returns undefined after reset"); t.end(); }); ``` --- --- url: /concepts/sandboxes/api/reset-history.md description: >- Resets the history of all [`fakes`](/concepts/fakes/), [`spies`](/concepts/spies/) and [`stubs`](/concepts/stubs/) created using the sandbox. --- # `sandbox.resetHistory();` Resets the history of all [`fakes`](/concepts/fakes/), [`spies`](/concepts/spies/) and [`stubs`](/concepts/stubs/) created using the sandbox. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test( "sandbox.resetHistory - resets history of fakes, spies and stubs", (t) => { // The sinon root object is a default sandbox const f = sinon.fake(); f(); t.ok(f.called, "fake was called"); // reset history for everything created using the default sandbox `sinon` sinon.resetHistory(); t.notOk(f.called, "fake history reset"); t.end(); } ); ``` --- --- url: /concepts/sandboxes/api/restore.md description: >- Restores all [`fakes`](/concepts/fakes/), [`spies`](/concepts/spies/) and [`stubs`](/concepts/stubs/) created through the sandbox. --- # `sandbox.restore();` Restores all [`fakes`](/concepts/fakes/), [`spies`](/concepts/spies/) and [`stubs`](/concepts/stubs/) created through the sandbox. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sandbox.restore - restores all fakes, spies and stubs", (t) => { // The sinon root object is a default sandbox const obj = { one: "1", two: "2", three: "3" }; sinon.replace(obj, "one", "apple"); sinon.replace(obj, "two", "banana"); sinon.replace(obj, "three", "cherry"); t.same( obj, { one: "apple", two: "banana", three: "cherry" }, "properties replaced" ); sinon.restore(); t.same(obj, { one: "1", two: "2", three: "3" }, "properties restored"); t.end(); }); ``` --- --- url: /concepts/sandboxes/api/spy.md description: 'Works exactly like [`sinon.spy`](/concepts/spies/)' --- # `sandbox.spy` Works exactly like [`sinon.spy`](/concepts/spies/) ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sandbox.spy and sandbox.assert - basic usage", (t) => { // The sinon root object is a default sandbox const spy = sinon.spy(); t.throws( () => sinon.assert.calledOnce(spy), /expected spy to be called once but was called 0 times/, "assert throws when spy not called" ); spy(); t.doesNotThrow( () => sinon.assert.calledOnce(spy), "assert does not throw after spy called once" ); t.end(); }); ``` --- --- url: /concepts/sandboxes/api/stub.md description: 'Works exactly like [`sinon.stub`](/concepts/stubs/).' --- # `sandbox.stub();` Works exactly like [`sinon.stub`](/concepts/stubs/). ## Stubbing a non-function property ```js import t from "tap"; import sinon from "sinon"; t.test("sandbox.stub can stub non-function properties", (t) => { const sandbox = sinon.createSandbox(); const myObject = { hello: "world" }; // Stub the property sandbox.stub(myObject, "hello").value("Sinon"); // Verify the stub works t.equal(myObject.hello, "Sinon", "property should be stubbed to 'Sinon'"); // Restore via sandbox sandbox.restore(); // Verify restoration t.equal(myObject.hello, "world", "property should be restored to 'world'"); t.end(); }); ``` --- --- url: /concepts/sandboxes/api/use-fake-timers.md description: >- Fakes the native timers and binds the `clock` object to the sandbox so it is restored when calling `sandbox.restore()`. --- # `sandbox.useFakeTimers();` Fakes the native timers and binds the `clock` object to the sandbox so it is restored when calling `sandbox.restore()`. Access `clock` through the returned object. ```js import t from "tap"; import sinon from "sinon"; import { JSDOM } from "jsdom"; t.test("sandbox.useFakeTimers binds clock to sandbox for auto-restore", (t) => { const dom = new JSDOM(""); const document = dom.window.document; const sandbox = sinon.createSandbox(); const clock = sandbox.useFakeTimers(); // Create element and add to document const el = document.createElement("div"); document.body.appendChild(el); // Animate function that changes styles over 500ms function animate(element) { setTimeout(() => { element.style.height = "200px"; element.style.width = "200px"; }, 500); } animate(el); // Verify animation hasn't completed yet t.equal(el.style.height, "", "height should be empty initially"); t.equal(el.style.width, "", "width should be empty initially"); // Advance time by 510ms clock.tick(510); // Verify animation completed t.equal(el.style.height, "200px", "height should be 200px after 510ms"); t.equal(el.style.width, "200px", "width should be 200px after 510ms"); // Restore via sandbox (clock is bound to sandbox) sandbox.restore(); t.end(); }); ``` --- --- url: /concepts/sandboxes/api/verify.md description: Verifies all mocks created through the sandbox. --- # `sandbox.verify();` Verifies all mocks created through the sandbox. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sandbox.verify - verifies all mocks", (t) => { // The sinon root object is a default sandbox const obj = { greet: function (name) { return `Hello ${name}`; } }; const mock = sinon.mock(obj); const expectation = mock.expects("greet"); // verify will throw because of the unmet expectation t.throws( () => sinon.verify(), /Expected greet\('\[...\]'\) once \(never called\)/, "throws when expectation not met" ); sinon.restore(); t.end(); }); ``` --- --- url: /concepts/sandboxes/api/verify-and-restore.md description: Verifies all mocks and restores all fakes created through the sandbox. --- # `sandbox.verifyAndRestore();` Verifies all mocks and restores all fakes created through the sandbox. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test( "sandbox.verifyAndRestore - verifies and restores even when verify fails", (t) => { // The sinon root object is a default sandbox const obj = { greet: function (name) { return `Hello ${name}`; } }; const mock = sinon.mock(obj); const expectation = mock.expects("greet"); // Do NOT call greet to have unmet expectation // obj.greet("Mickey Mouse"); // mocked methods have a restore method on them t.equal( typeof obj.greet.restore, "function", "mocked method has restore function" ); // verify will throw because of the unmet expectation t.throws( () => sinon.verifyAndRestore(), /Expected greet\('\[...\]'\) once \(never called\)/, "throws when expectation not met" ); // but the restore part will still be performed // the original greet method has been restored t.equal( typeof obj.greet.restore, "undefined", "method restored even though verify failed" ); t.end(); } ); ``` --- --- url: /concepts/sandboxes/api/assert.md description: 'A convenience reference for [sinon.assert](/concepts/assertions/)' --- # `sandbox.assert` A convenience reference for [sinon.assert](/concepts/assertions/) ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sandbox.spy and sandbox.assert - basic usage", (t) => { // The sinon root object is a default sandbox const spy = sinon.spy(); t.throws( () => sinon.assert.calledOnce(spy), /expected spy to be called once but was called 0 times/, "assert throws when spy not called" ); spy(); t.doesNotThrow( () => sinon.assert.calledOnce(spy), "assert does not throw after spy called once" ); t.end(); }); ``` --- --- url: /concepts/sandboxes/api/leak-threshold.md description: Gets/sets the threshold at which memory leak detection warnings are logged. --- # `sandbox.leakThreshold` Gets/sets the threshold at which memory leak detection warnings are logged. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sandbox.leakThreshold - basic usage", (t) => { // Use a custom sandbox since leakThreshold is read-only on the module const sandbox = sinon.createSandbox(); const threshold = 1; sandbox.leakThreshold = threshold; t.equal(sandbox.leakThreshold, threshold, "leak threshold set to 1"); // Creating fakes will trigger warning when threshold exceeded // (not testing the warning itself, just that the property works) sandbox.fake(); sandbox.restore(); t.end(); }); ``` --- --- url: /concepts/spy-call.md description: >- Access individual call details including arguments, return values, this context, and exceptions for each invocation. --- # Spy call A spy call is an object representation of an individual call to a *spied* function, which could be a [fake][fakes], [spy][spies], [stub][stubs] or [mock method][mocks]. ## `fake.getCall(n)` Returns a spyCall for the `nth` call to the fake. Accessing individual calls helps with more detailed behavior verification when the fake is called more than once. [matchers]: /concepts/matchers/ [fakes]: /concepts/fakes/ [spies]: /concepts/spies/ [stubs]: /concepts/stubs/ [mocks]: /concepts/mocks/ --- --- url: /concepts/spy-call/api/called-after.md description: 'Returns `true`, when the spy call occurred after another spy call.' --- # `spyCall.calledAfter(otherCall)` Returns `true`, when the spy call occurred after another spy call. ```js import t from "tap"; import sinon from "sinon"; t.test( `spyCall.calledAfter returns true, when instance is called after the argument`, (t) => { const f = sinon.fake(); f(); f(); f(); const firstCall = f.firstCall; const lastCall = f.lastCall; t.ok(lastCall.calledAfter(firstCall)); t.end(); } ); ``` --- --- url: /concepts/spy-call/api/called-before.md description: 'Returns `true`, when the spy call occurred before another spy call.' --- # `spyCall.calledBefore(otherCall)` Returns `true`, when the spy call occurred before another spy call. ```js import t from "tap"; import sinon from "sinon"; t.test( `spyCall.calledBefore returns true, when instance is called before the argument`, (t) => { const f = sinon.fake(); f(); f(); f(); const firstCall = f.firstCall; const lastCall = f.lastCall; t.ok(firstCall.calledBefore(lastCall)); t.end(); } ); ``` --- --- url: /concepts/spy-call/api/called-immediately-after.md description: >- Returns `true`, when the spy call occurred after another call, and no calls to any spy occurred in between. --- # `spyCall.calledImmediatelyAfter(otherCall)` Returns `true`, when the spy call occurred after another call, and no calls to any spy occurred in between. ```js import t from "tap"; import sinon from "sinon"; t.test( `spyCall.calledImmediatelyAfter returns true, when instance is called immediately after the argument`, (t) => { const f1 = sinon.fake(); const f2 = sinon.fake(); f1(); f2(); const f1Call = f1.firstCall; const f2Call = f2.firstCall; t.ok(f2Call.calledImmediatelyAfter(f1Call)); t.end(); } ); t.test( `spyCall.calledImmediatelyAfter returns true, when instance is not called immediately after the argument`, (t) => { const f1 = sinon.fake(); const f2 = sinon.fake(); f1(); f1(); f2(); const f1Call = f1.firstCall; const f2Call = f2.firstCall; t.notOk(f2Call.calledImmediatelyAfter(f1Call)); t.end(); } ); ``` --- --- url: /concepts/spy-call/api/called-immediately-before.md description: >- Returns `true`, when the spy call occurred before another call, and no calls to any spy occurred in between. --- # `spyCall.calledImmediatelyBefore(otherCall)` Returns `true`, when the spy call occurred before another call, and no calls to any spy occurred in between. ```js import t from "tap"; import sinon from "sinon"; t.test( `spyCall.calledImmediatelyBefore returns true, when instance is called immediately before the argument`, (t) => { const f1 = sinon.fake(); const f2 = sinon.fake(); f1(); f2(); const f1Call = f1.firstCall; const f2Call = f2.firstCall; t.end(); } ); t.test( `spyCall.calledImmediatelyBefore returns true, when instance is not called immediately before the argument`, (t) => { const f1 = sinon.fake(); const f2 = sinon.fake(); f1(); f1(); f2(); const f1Call = f1.firstCall; const f2Call = f2.firstCall; t.notOk(f1Call.calledImmediatelyBefore(f2Call)); t.end(); } ); ``` --- --- url: /concepts/spy-call/api/called-on.md description: Returns `true` when `obj` was context (`this`) for the call. --- # `spyCall.calledOn(obj);` Returns `true` when `obj` was context (`this`) for the call. ```js import t from "tap"; import sinon from "sinon"; t.test( `spyCall.calledOn returns true, when argument is context`, (t) => { const f = sinon.fake(); const context = {}; f.apply(context); const firstCall = f.firstCall; t.ok(firstCall.calledOn(context)); t.end(); } ); ``` ## Using a matcher `calledOn` also accepts a matcher `spyCall.calledOn(sinon.match(fn))` (see [matchers](/concepts/matchers/)). ```js import t from "tap"; import sinon from "sinon"; t.test( `spyCall.calledOn returns true, when argument is context`, (t) => { const f = sinon.fake(); const context = { author: "cjno", hello: "world" }; f.apply(context); const firstCall = f.firstCall; t.ok(firstCall.calledOn(sinon.match({ author: "cjno" }))); t.end(); } ); ``` ## See also * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this --- --- url: /concepts/spy-call/api/called-with.md description: >- Returns `true`, when the spy call received provided arguments in same positions, (and possibly further arguments). --- # `spyCall.calledWith(arg1, arg2, ...);` Returns `true`, when the spy call received provided arguments in same positions, (and possibly further arguments). ```js import t from "tap"; import sinon from "sinon"; t.test( `spyCall.calledWith returns true, when instance received provided arguments`, (t) => { const f = sinon.fake(); f("apple pie", Math.PI, "cherry pie"); const sc = f.firstCall; t.ok(sc.calledWith("apple pie", Math.PI)); t.end(); } ); ``` --- --- url: /concepts/spy-call/api/called-with-exactly.md description: >- Returns `true`, when the spy call received provided arguments in exact same order and received no other arguments. --- # `spyCall.calledWithExactly(arg1, arg2, ...);` Returns `true`, when the spy call received provided arguments in exact same order and received no other arguments. ```js import t from "tap"; import sinon from "sinon"; t.test( `spyCall.calledWithExactly returns true, when instance received provided arguments in exact same order`, (t) => { const f = sinon.fake(); f("apple pie", Math.PI); const sc = f.firstCall; t.ok(sc.calledWithExactly("apple pie", Math.PI)); t.end(); } ); t.test( `spyCall.calledWithExactly returns false, when received additional arguments`, (t) => { const f = sinon.fake(); f("apple pie", Math.PI, "cherry pie"); const sc = f.firstCall; t.notOk(sc.calledWithExactly("apple pie", Math.PI)); t.end(); } ); ``` --- --- url: /concepts/spy-call/api/called-with-match.md description: >- Returns `true`, when the spy call received matching arguments (and possibly others). --- # `spyCall.calledWithMatch(arg1, arg2, ...);` Returns `true`, when the spy call received matching arguments (and possibly others). This behaves the same as [`spyCall.calledWith(sinon.match(arg1), sinon.match(arg2), ...)`](./called-with). ```js import t from "tap"; import sinon from "sinon"; t.test( `spyCall.calledWithMatch returns true, when instance received matching arguments`, (t) => { const f = sinon.fake(); const dish = { name: "apple pie", price: Math.PI }; f(dish); const sc = f.firstCall; t.ok(sc.calledWithMatch(sinon.match({ name: "apple pie" }))); t.end(); } ); ``` --- --- url: /concepts/spy-call/api/not-called-with.md description: 'Returns `true`, when the spy call did not receive provided arguments.' --- # `spyCall.notCalledWith(arg1, arg2, ...);` Returns `true`, when the spy call did not receive provided arguments. ```js import t from "tap"; import sinon from "sinon"; t.test( `spyCall.notCalledWith returns true, when instance did not receive provided arguments`, (t) => { const f = sinon.fake(); f("apple pie", Math.PI); const sc = f.firstCall; t.ok(sc.notCalledWith("cherry pie")); t.end(); } ); ``` --- --- url: /concepts/spy-call/api/not-called-with-match.md description: >- Returns `true`, when the spyt call did not receive matching arguments. This behaves the same as [`spyCall.notCalledWith(sinon.match(arg1), sinon.match(arg2), ...)`](./not-called-with). --- # `spyCall.notCalledWithMatch(arg1, arg2, ...);` Returns `true`, when the spyt call did not receive matching arguments. This behaves the same as [`spyCall.notCalledWith(sinon.match(arg1), sinon.match(arg2), ...)`](./not-called-with). ```js import t from "tap"; import sinon from "sinon"; t.test( `spyCall.notCalledWithMatch returns true, when instance received matching arguments`, (t) => { const f = sinon.fake(); const dish = { name: "apple pie", price: Math.PI }; f(dish); const sc = f.firstCall; t.ok(sc.notCalledWithMatch(sinon.match({ name: "cherry pie" }))); t.end(); } ); ``` --- --- url: /concepts/spy-call/api/returned.md description: >- Returns `true`, when the spied function returned the provided `value` on this call. --- # `spyCall.returned(value);` Returns `true`, when the spied function returned the provided `value` on this call. Uses deep comparison for objects and arrays. Use `spyCall.returned(sinon.match.same(obj))` for strict comparison (see [matchers](/concepts/matchers/)). ```js import t from "tap"; import sinon from "sinon"; t.test( `spyCall.returned returns true, when spied function returned value on this call`, (t) => { const pie = "apple pie"; const f = sinon.fake.returns(pie); f(); const sc = f.firstCall; t.ok(sc.returned(pie)); t.end(); } ); ``` --- --- url: /concepts/spy-call/api/threw.md description: 'Returns `true`, when the spied function threw on this call.' --- # `spyCall.threw();` Returns `true`, when the spied function threw on this call. ```js import t from "tap"; import sinon from "sinon"; t.test( `spyCall.threw returns true, when spied function threw on this call`, (t) => { const f = sinon.fake.throws("The pie is a lie"); let sc; try { f(); } catch (ex) { sc = f.firstCall; } t.ok(sc.threw()); t.end(); } ); ``` ## `spyCall.threw("TypeError");` Returns `true`, when the spied function threw provided type on this call. ```js import t from "tap"; import sinon from "sinon"; t.test( `spyCall.threw returns true, when spied function threw provided error type on this call`, (t) => { const f = sinon.fake.throws(new TypeError("The pie is a lie")); let sc; try { f(); } catch (ex) { sc = f.firstCall; } t.ok(sc.threw("TypeError")); t.end(); } ); ``` ## `spyCall.threw(obj);` Returns `true`, when the spied function threw provided object on this call. ```js import t from "tap"; import sinon from "sinon"; t.test( `spyCall.threw returns true, when spied function threw provided object on this call`, (t) => { const error = new TypeError("The pie is a lie"); const f = sinon.fake.throws(error); let sc; try { f(); } catch (ex) { sc = f.firstCall; } t.ok(sc.threw(error)); t.end(); } ); ``` --- --- url: /concepts/spy-call/api/args.md description: >- This property contains an array of arrays containing received arguments for each call. --- # `spyCall.args` This property contains an array of arrays containing received arguments for each call. ```js import t from "tap"; import sinon from "sinon"; t.test( `spyCall.args contains an array of arrays containing received arguments for each call`, (t) => { const f = sinon.fake(); f("apple pie"); f("banana pie"); f("cherry pie"); t.same(f.args, [["apple pie"], ["banana pie"], ["cherry pie"]]); t.end(); } ); ``` --- --- url: /concepts/spy-call/api/callback.md description: This property is a convenience for a call's callback. --- # `spyCall.callback` This property is a convenience for a call's callback. When the last argument in a call is a `Function`, then `callback` will reference that. Otherwise it will be `undefined`. ```js import t from "tap"; import sinon from "sinon"; t.test(`spyCall.callback contains the callback of the call`, (t) => { const f = sinon.fake(); const callback = function () {}; f(1, 2, 3, callback); t.equal(callback, f.lastCall.callback); f(4, 5, 6); t.equal(undefined, f.lastCall.callback); t.end(); }); ``` --- --- url: /concepts/spy-call/api/exception.md description: '' --- # `spyCall.exception` When a fake throws an error during a call, this property will contain a reference to it, otherwise it will be `undefined`. ```js import t from "tap"; import sinon from "sinon"; t.test(`spyCall.exception contains the error thrown`, (t) => { const error = new Error("The pie is a lie"); const f = sinon.fake.throws(error); try { f(); } catch (thrownError) { t.equal(thrownError, f.lastCall.exception); } t.equal(error, f.lastCall.exception); t.end(); }); t.test(`spyCall.exception is undefined when no error thrown`, (t) => { const f = sinon.fake(); f(); t.equal(undefined, f.lastCall.exception); t.end(); }); ``` --- --- url: /concepts/spy-call/api/first-arg.md description: This property contains a reference to the first argument of the call. --- # `spyCall.firstArg` This property contains a reference to the first argument of the call. ```js import t from "tap"; import sinon from "sinon"; t.test( `spyCall.firstArg contains a reference to the first argument of the call`, (t) => { const f = sinon.fake(); f("apple pie", "banana pie"); t.equal(f.lastCall.firstArg, "apple pie"); f(); t.equal(f.lastCall.firstArg, undefined); t.end(); } ); ``` --- --- url: /concepts/spy-call/api/last-arg.md description: This property contains a reference to the last argument of the call. --- # `spyCall.lastArg` This property contains a reference to the last argument of the call. ```js import t from "tap"; import sinon from "sinon"; t.test( `spyCall.lastArg contains a reference to the last argument of the call`, (t) => { const f = sinon.fake(); f("apple pie", "banana pie"); t.equal(f.lastCall.lastArg, "banana pie"); f(); t.equal(f.lastCall.lastArg, undefined); t.end(); } ); ``` --- --- url: /concepts/spy-call/api/return-value.md description: This property contains a reference to the value returned from the call. --- ## `spyCall.returnValue` This property contains a reference to the value returned from the call. ```js import t from "tap"; import sinon from "sinon"; t.test( `spyCall.returnValue contains a reference to the value returned from the call`, (t) => { const pie = { name: "apple pie" }; const f1 = sinon.fake.returns(pie); const f2 = sinon.fake(); f1(); t.equal(f1.lastCall.returnValue, pie); f2(); t.equal(f2.lastCall.returnValue, undefined); t.end(); } ); ``` --- --- url: /concepts/spy-call/api/this-value.md description: '' --- # `spyCall.thisValue` This property contains a reference to the context (`this`) used in the call. ```js import t from "tap"; import sinon from "sinon"; t.test( `spyCall.thisValue contains a reference to the context ("this") used in the call`, (t) => { const context = {}; const f = sinon.fake(); f.apply(context); t.equal(f.lastCall.thisValue, context); f(); t.equal(f.lastCall.thisValue, this); const object = { method: sinon.fake() }; object.method(); t.equal(object.method.lastCall.thisValue, object); t.end(); } ); ``` ## See also * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this --- --- url: /concepts/fake-timers.md description: >- Replace setTimeout, setInterval, Date, and Temporal with controllable fake implementations for time-based testing. --- # Fake timers Fake timers are synchronous implementations of `setTimeout` and friends that Sinon.JS can overwrite the global functions with to allow you to more easily test code using them. It also has utilities for working with `async`/Promise code. Fake timers provide a `clock` object to pass time, which can also be used to control `Date` objects (e.g. `new Date()`) and the `Temporal` API (e.g. `Temporal.Now.instant()`). For standalone usage of fake timers it is recommended to use [fake-timers](https://github.com/sinonjs/fake-timers) package instead. It provides the same set of features (Sinon uses it under the hood) and was previously extracted from Sinon.JS. ```js import t from "tap"; import sinon from "sinon"; import { JSDOM } from "jsdom"; t.test("fake timers can control time for animations and timeouts", (t) => { const dom = new JSDOM(""); const document = dom.window.document; const clock = sinon.useFakeTimers(); // Create element and add to document const el = document.createElement("div"); document.body.appendChild(el); // Animate function that changes styles over 500ms function animate(element) { setTimeout(() => { element.style.height = "200px"; element.style.width = "200px"; }, 500); } animate(el); // Verify animation hasn't completed yet t.equal(el.style.height, "", "height should be empty initially"); t.equal(el.style.width, "", "width should be empty initially"); // Advance time by 510ms clock.tick(510); // Verify animation completed t.equal(el.style.height, "200px", "height should be 200px after 510ms"); t.equal(el.style.width, "200px", "width should be 200px after 510ms"); clock.restore(); t.end(); }); ``` --- --- url: /concepts/fake-timers/use-fake-timers.md description: >- Replaces global timers with fake implementations. Configurable with now, toFake, shouldAdvanceTime, and more. --- # `sinon.useFakeTimers([config])` Causes Sinon to replace the global `setTimeout`, `clearTimeout`, `setInterval`, `clearInterval`, `setImmediate`, `clearImmediate`, `process.hrtime`, `performance.now` (when available) and `Date` with a custom implementation which is bound to the returned `clock` object. In Node.js, the `timers` and `timers/promises` modules will also receive fake timers when using the global scope. ## Without arguments Starts the clock at the UNIX epoch (timestamp of `0`). ## With `now` argument As above, but rather than starting the clock with a timestamp of 0, start at the provided timestamp `now`. You can also pass in a Date object, and its `getTime()` will be used for the starting timestamp. ```js // Start at January 1st 2017 const clock = sinon.useFakeTimers(1483228800000); ``` ## With config object As above, but allows further configuration options. ```js // Start at a specific time with a custom loop limit const clock = sinon.useFakeTimers({ now: 1483228800000, loopLimit: 10 }); ``` ### Configuration Options | Option | Type | Default | Description | | -------------------------------- | ----------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `config.now` | Number/Date | 0 | Installs fake timers with the specified unix epoch | | `config.toFake` | String\[] | All except nextTick | An array with explicit function names to fake. Cannot be combined with `toNotFake` | | `config.toNotFake` | String\[] | \[] | An array with explicit function names that should remain native. Cannot be combined with `toFake` | | `config.loopLimit` | Number | 1000 | The maximum number of timers that will be run when calling `runAll()` | | `config.shouldAdvanceTime` | Boolean | false | Tells fake timers to increment mocked time automatically based on real system time shift | | `config.advanceTimeDelta` | Number | 20 | Relevant only when using `shouldAdvanceTime: true`. Increment mocked time by this many ms for every ms change in real time | | `config.shouldClearNativeTimers` | Boolean | false | Tells fake timers to clear native (non-fake) timers before installing | | `config.ignoreMissingTimers` | Boolean | false | Tells fake timers to ignore missing timers that might not exist in the given environment | | `config.target` | Object | global | Use a specific object instead of the usual global object. Useful with JSDOM | ### `config.toFake` By default, fake timers will automatically fake all methods except `process.nextTick`. You can explicitly specify which methods to fake: ```js sinon.useFakeTimers({ toFake: ["setTimeout", "nextTick"] }); ``` To fake everything including `nextTick`: ```js sinon.useFakeTimers({ toFake: [ "setTimeout", "clearTimeout", "setInterval", "clearInterval", "setImmediate", "clearImmediate", "Date", "nextTick", "hrtime", "performance" ] }); ``` ### `config.toNotFake` Instead of specifying what to fake, you can specify what NOT to fake: ```js sinon.useFakeTimers({ toNotFake: ["Date"] }); ``` This will fake all supported methods except `Date`. ### `config.shouldAdvanceTime` This tells fake timers to automatically advance time based on real system time changes. Useful when you don't know when to call `tick()`: ```js sinon.useFakeTimers({ shouldAdvanceTime: true }); ``` Note: This uses `setInterval` at a configurable interval (default 20ms) to check for time changes, not real-time advancement. ### `config.target` Useful when using JSDOM or other sandboxed environments: ```js sinon.useFakeTimers({ target: jsdomWindow }); ``` ### Example with multiple config options ```js import t from "tap"; import sinon from "sinon"; t.test("useFakeTimers with nextTick requires manual flushing", (t) => { const clock = sinon.useFakeTimers({ now: 1483228800000, toFake: ["setTimeout", "nextTick"] }); let called = false; process.nextTick(function () { called = true; }); // nextTick doesn't execute automatically t.notOk(called, "callback should not be called yet"); // Forces nextTick calls to flush synchronously clock.runAll(); // Verify callback was executed t.ok(called, "callback should be called after runAll"); clock.restore(); t.end(); }); ``` ### With `shouldAdvanceTime` example ```js import t from "tap"; import sinon from "sinon"; t.test( "useFakeTimers with shouldAdvanceTime runs timers automatically", async (t) => { const clock = sinon.useFakeTimers({ now: 1483228800000, shouldAdvanceTime: true }); const immediate = sinon.fake(); const timeout1 = sinon.fake(); const timeout2 = sinon.fake(); setImmediate(immediate); setTimeout(timeout1, 15); setTimeout(timeout2, 35); // Wait for auto-advancement to trigger timers await new Promise((resolve) => setTimeout(resolve, 50)); // Verify all callbacks were executed t.ok(immediate.calledOnce, "setImmediate callback should be called"); t.ok(timeout1.calledOnce, "first setTimeout callback should be called"); t.ok(timeout2.calledOnce, "second setTimeout callback should be called"); clock.restore(); t.end(); } ); ``` ### Using async/await ```js import t from "tap"; import sinon from "sinon"; function wait(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } t.test("useFakeTimers with async/await using tickAsync", async (t) => { const clock = sinon.useFakeTimers(); const logs = []; async function asyncFn() { await wait(100); logs.push({ msg: "resolved 1", time: Date.now() }); await wait(10); logs.push({ msg: "resolved 2", time: Date.now() }); } setTimeout(() => logs.push({ msg: "timeout", time: Date.now() }), 200); // NOTE: no `await` here - it would hang, as the clock is stopped asyncFn(); await clock.tickAsync(200); // Verify all async operations completed in correct order t.equal(logs.length, 3, "should have 3 log entries"); t.same(logs[0], { msg: "resolved 1", time: 100 }); t.same(logs[1], { msg: "resolved 2", time: 110 }); t.same(logs[2], { msg: "timeout", time: 200 }); clock.restore(); t.end(); }); ``` --- --- url: /concepts/fake-timers/count-timers.md description: Returns the number of waiting timers. --- # `clock.countTimers()` Returns the number of waiting timers. ```js import t from "tap"; import sinon from "sinon"; t.test("clock.countTimers returns zero for a fresh clock", (t) => { const clock = sinon.useFakeTimers(); t.equal(clock.countTimers(), 0, "fresh clock should have no pending timers"); clock.restore(); t.end(); }); t.test("clock.countTimers counts remaining timers after a tick", (t) => { const clock = sinon.useFakeTimers(); setTimeout(() => {}, 100); setTimeout(() => {}, 200); setTimeout(() => {}, 300); clock.tick(150); t.equal(clock.countTimers(), 2, "two timers should remain after 150ms"); clock.restore(); t.end(); }); t.test("clock.countTimers includes microtasks such as nextTick", (t) => { const clock = sinon.useFakeTimers({ toFake: ["nextTick"] }); process.nextTick(() => {}); t.equal( clock.countTimers(), 1, "nextTick callback should count as a pending timer" ); clock.restore(); t.end(); }); ``` --- --- url: /concepts/fake-timers/jump.md description: >- Advance the clock by jumping forward in time, firing callbacks at most once. Useful for simulating a JS engine being put to sleep. --- # `clock.jump(time)` Advance the clock by jumping forward in time, firing callbacks at most once. `time` takes the same formats as [`clock.tick`](./tick). This can be used to simulate the JS engine (such as a browser) being put to sleep and resumed later, skipping intermediary timers. ```js import t from "tap"; import sinon from "sinon"; t.test("clock.jump ignores timers not within the jump window", (t) => { const clock = sinon.useFakeTimers({ now: 0 }); let called = false; setTimeout(() => { called = true; }, 1000); clock.jump(500); t.notOk(called, "timer beyond jump window should not have fired"); clock.restore(); t.end(); }); t.test("clock.jump fires timers within the jump window at the destination time", (t) => { const clock = sinon.useFakeTimers({ now: 0 }); let calledAt = null; setTimeout(() => { calledAt = Date.now(); }, 1000); clock.jump(2000); t.equal(calledAt, 2000, "timer should fire and see the jump destination as the current time"); clock.restore(); t.end(); }); t.test( "clock.jump fires each interval at most once regardless of elapsed time", (t) => { const clock = sinon.useFakeTimers({ now: 0 }); let callCount = 0; setInterval(() => { callCount++; }, 100); // A plain tick(1500) would fire the interval ~15 times; jump fires it once clock.jump(1500); t.equal(callCount, 1, "interval should have fired at most once"); clock.restore(); t.end(); } ); t.test("clock.jump supports human-readable string time arguments", (t) => { const clock = sinon.useFakeTimers({ now: 0 }); let called = false; setTimeout(() => { called = true; }, 100000); // 1 minute 40 seconds clock.jump("01:50"); // 1 minute 50 seconds t.ok(called, "timer should have fired after string-format jump"); clock.restore(); t.end(); }); ``` --- --- url: /concepts/fake-timers/next.md description: >- Advances the clock to the next scheduled timer and fires it. Use nextAsync for promise-based callbacks. --- # `clock.next()` / `await clock.nextAsync()` Advances the clock to the the moment of the first scheduled timer, firing it. The `nextAsync()` will also break the event loop, allowing any scheduled promise callbacks to execute *before* running the timers. ```js import t from "tap"; import sinon from "sinon"; t.test("clock.next advances to the next scheduled timer", (t) => { const clock = sinon.useFakeTimers(); let firstCalled = false; let secondCalled = false; setTimeout(() => { firstCalled = true; }, 10); setTimeout(() => { secondCalled = true; }, 20); // Advance to first timer clock.next(); t.ok(firstCalled, "first callback should have fired"); t.notOk(secondCalled, "second callback should not have fired yet"); // Advance to second timer clock.next(); t.ok(secondCalled, "second callback should have fired"); clock.restore(); t.end(); }); t.test("clock.nextAsync with promises", async (t) => { const clock = sinon.useFakeTimers(); let called = false; setTimeout(() => { called = true; }, 10); // nextAsync breaks the event loop first await clock.nextAsync(); t.ok(called, "callback should have fired"); clock.restore(); t.end(); }); ``` --- --- url: /concepts/fake-timers/now.md description: Returns the current fake time in milliseconds. --- # `clock.now` Returns the current fake time in milliseconds. ```js import t from "tap"; import sinon from "sinon"; t.test("clock.now returns the current fake time", (t) => { const clock = sinon.useFakeTimers({ now: 1000 }); t.equal(clock.now, 1000, "clock.now should be initial timestamp"); clock.tick(500); t.equal(clock.now, 1500, "clock.now should advance after tick"); clock.restore(); t.end(); }); t.test("clock.now with Date object initialization", (t) => { const startDate = new Date("2020-01-01T00:00:00Z"); const clock = sinon.useFakeTimers(startDate); t.equal( clock.now, startDate.getTime(), "clock.now should match Date.getTime()" ); clock.restore(); t.end(); }); ``` --- --- url: /concepts/fake-timers/reset.md description: Resets the clock to its initial now value and clears all pending timers. --- # `clock.reset()` Resets the clock to its initial `now` value and clears all pending timers. ```js import t from "tap"; import sinon from "sinon"; t.test( "clock.reset empties the queue and returns to the install time", (t) => { const stub = sinon.stub(); const clock = sinon.useFakeTimers(); clock.setSystemTime(1000); setTimeout(stub); clock.reset(); clock.tick(0); t.notOk(stub.called, "callback queued before reset should not fire"); t.equal(Date.now(), 0, "clock should reset to initial time"); clock.restore(); t.end(); } ); t.test("clock.reset returns to the timestamp the clock was installed with", (t) => { const clock = sinon.useFakeTimers({ now: 10000 }); clock.tick(5000); t.equal(clock.now, 15000, "clock should have advanced"); clock.reset(); t.equal(clock.now, 10000, "clock should return to install time"); clock.restore(); t.end(); }); ``` --- --- url: /concepts/fake-timers/restore.md description: 'Restores the faked methods, returning time to normal.' --- # `clock.restore()` Restore the faked methods. Call in e.g. `tearDown`. ```js import t from "tap"; import sinon from "sinon"; t.test("useFakeTimers with nextTick requires manual flushing", (t) => { const clock = sinon.useFakeTimers({ now: 1483228800000, toFake: ["setTimeout", "nextTick"] }); let called = false; process.nextTick(function () { called = true; }); // nextTick doesn't execute automatically t.notOk(called, "callback should not be called yet"); // Forces nextTick calls to flush synchronously clock.runAll(); // Verify callback was executed t.ok(called, "callback should be called after runAll"); clock.restore(); t.end(); }); ``` --- --- url: /concepts/fake-timers/run-all.md description: >- Runs all pending timers until there are none remaining. Use runAllAsync for promise-based callbacks. --- # `clock.runAll()` / `await clock.runAllAsync()` This runs all pending timers until there are none remaining. If new timers are added while it is executing they will be run as well. This makes it easier to run asynchronous tests to completion without worrying about the number of timers they use, or the delays in those timers. The `runAllAsync()` will also break the event loop, allowing any scheduled promise callbacks to execute *before* running the timers. ```js import t from "tap"; import sinon from "sinon"; t.test("useFakeTimers with nextTick requires manual flushing", (t) => { const clock = sinon.useFakeTimers({ now: 1483228800000, toFake: ["setTimeout", "nextTick"] }); let called = false; process.nextTick(function () { called = true; }); // nextTick doesn't execute automatically t.notOk(called, "callback should not be called yet"); // Forces nextTick calls to flush synchronously clock.runAll(); // Verify callback was executed t.ok(called, "callback should be called after runAll"); clock.restore(); t.end(); }); ``` --- --- url: /concepts/fake-timers/run-microtasks.md description: Runs all pending microtasks such as process.nextTick or Promise callbacks. --- # `clock.runMicrotasks()` Runs all pending microtasks (e.g. `process.nextTick` or `Promise` callbacks). ```js import t from "tap"; import sinon from "sinon"; t.test( "clock.runMicrotasks flushes process.nextTick without advancing time", (t) => { const clock = sinon.useFakeTimers({ toFake: ["nextTick"] }); let called = false; process.nextTick(() => { called = true; }); t.notOk(called, "nextTick callback should not have run yet"); clock.runMicrotasks(); t.ok(called, "nextTick callback should have run"); t.equal(clock.now, 0, "clock time should not have advanced"); clock.restore(); t.end(); } ); t.test("clock.runMicrotasks flushes queueMicrotask callbacks", (t) => { const clock = sinon.useFakeTimers({ toFake: ["queueMicrotask"] }); let called = false; queueMicrotask(() => { called = true; }); clock.runMicrotasks(); t.ok(called, "queueMicrotask callback should have run"); clock.restore(); t.end(); }); t.test("clock.runMicrotasks does not fire setTimeout callbacks", (t) => { const clock = sinon.useFakeTimers(); let timerCalled = false; setTimeout(() => { timerCalled = true; }, 0); clock.runMicrotasks(); t.notOk(timerCalled, "setTimeout callback should not have fired"); clock.restore(); t.end(); }); ``` --- --- url: /concepts/fake-timers/run-to-frame.md description: Advances the clock to the next animation frame (standard 16ms). --- # `clock.runToFrame()` Advances the clock to the next animation frame (standard 16ms). ```js import t from "tap"; import sinon from "sinon"; t.test("clock.runToFrame advances the clock to successive 16ms boundaries", (t) => { const clock = sinon.useFakeTimers(); clock.runToFrame(); t.equal(clock.now, 16, "clock should be at 16ms after first frame"); clock.tick(3); // now at 19ms clock.runToFrame(); t.equal(clock.now, 32, "clock should be at 32ms after second frame"); clock.restore(); t.end(); }); t.test("clock.runToFrame fires timers scheduled within the frame", (t) => { const clock = sinon.useFakeTimers(); let called = false; setTimeout(() => { called = true; }, 16); clock.runToFrame(); t.ok(called, "timer scheduled at 16ms should have fired"); clock.restore(); t.end(); }); ``` --- --- url: /concepts/fake-timers/run-to-last.md description: >- Runs all pending timers until the last timer has been fired. Use runToLastAsync for promise-based callbacks. --- # `clock.runToLast()` / `await clock.runToLastAsync()` This runs all pending timers until the last timer has been fired. If new timers are added while it is executing they will be run as well. The `runToLastAsync()` will also break the event loop, allowing any scheduled promise callbacks to execute *before* running the timers. ```js import t from "tap"; import sinon from "sinon"; t.test( "clock.runToLast runs all pending timers and returns the time of the last", (t) => { const clock = sinon.useFakeTimers(); const calls = []; setTimeout(() => calls.push("first"), 10); setTimeout(() => calls.push("second"), 50); const time = clock.runToLast(); t.same(calls, ["first", "second"], "all timers should have fired in order"); t.equal(time, 50, "should return the time of the last timer"); clock.restore(); t.end(); } ); t.test( "clock.runToLast does not run timers added beyond the last scheduled time", (t) => { const clock = sinon.useFakeTimers(); let laterCalled = false; setTimeout(() => { // This new timer (10ms + 50ms = 60ms) is beyond the original last timer (10ms) setTimeout(() => { laterCalled = true; }, 50); }, 10); clock.runToLast(); t.notOk( laterCalled, "timer added beyond the last scheduled time should not run" ); clock.restore(); t.end(); } ); t.test( "clock.runToLastAsync allows promise callbacks to execute before timers", async (t) => { const clock = sinon.useFakeTimers(); const order = []; Promise.resolve().then(() => order.push("microtask")); setTimeout(() => order.push("timer"), 100); await clock.runToLastAsync(); t.ok( order.indexOf("microtask") < order.indexOf("timer"), "microtask should have run before the timer" ); clock.restore(); t.end(); } ); ``` --- --- url: /concepts/fake-timers/set-system-time.md description: Change the system time without firing any timers. --- # `clock.setSystemTime([now])` This allows you to change the system time to the provided `now` (number or Date) without firing any timers. ```js import t from "tap"; import sinon from "sinon"; t.test( "clock.setSystemTime changes the visible time without affecting timer scheduling", (t) => { const stub = sinon.stub(); const clock = sinon.useFakeTimers(); clock.setTimeout(stub, 5000); clock.tick(1000); // Shift the displayed time forward by 1000ms — the timer schedule is unaffected clock.setSystemTime(new clock.Date().getTime() + 1000); clock.tick(3990); t.equal(stub.callCount, 0, "timer should not have fired yet"); clock.tick(20); t.equal(stub.callCount, 1, "timer should fire after the remaining real ticks"); clock.restore(); t.end(); } ); t.test("clock.setSystemTime accepts a numeric timestamp", (t) => { const clock = sinon.useFakeTimers(); clock.setSystemTime(5000); t.equal(Date.now(), 5000, "Date.now() should reflect the new system time"); clock.restore(); t.end(); }); t.test("clock.setSystemTime accepts a Date object", (t) => { const clock = sinon.useFakeTimers(); const target = new Date("2025-01-01T00:00:00Z"); clock.setSystemTime(target); t.equal( Date.now(), target.getTime(), "Date.now() should match the provided Date" ); clock.restore(); t.end(); }); ``` --- --- url: /concepts/fake-timers/tick.md description: >- Advances the clock by the specified time in milliseconds. Supports human-readable strings like "01:00" and Temporal.Duration objects. --- # `clock.tick(time)` / `await clock.tickAsync(time)` Tick the clock ahead `time` milliseconds. Causes all timers scheduled within the affected time range to be called. `time` may be the number of milliseconds to advance the clock by, a human-readable string, or a `Temporal.Duration` object. Valid string formats are "08" for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds. ```javascript clock.tick(Temporal.Duration.from({ hours: 1, minutes: 30 })); ``` The `tickAsync()` will also break the event loop, allowing any scheduled promise callbacks to execute *before* running the timers. ```js import t from "tap"; import sinon from "sinon"; t.test("clock.tick advances time and fires timers", (t) => { const clock = sinon.useFakeTimers(); let callCount = 0; setTimeout(() => { callCount++; }, 100); setTimeout(() => { callCount++; }, 200); // Before ticking, callbacks should not have fired t.equal(callCount, 0, "callbacks should not have fired yet"); // Tick past the first timeout clock.tick(100); t.equal(callCount, 1, "first callback should have fired"); // Tick past the second timeout clock.tick(100); t.equal(callCount, 2, "second callback should have fired"); clock.restore(); t.end(); }); t.test("clock.tick accepts human-readable strings", (t) => { const clock = sinon.useFakeTimers(); let called = false; setTimeout(() => { called = true; }, 5000); // "00:00:05" = 5 seconds clock.tick("00:00:05"); t.ok(called, "callback should have fired after 5 seconds"); clock.restore(); t.end(); }); t.test( "clock.tickAsync allows promise callbacks to execute first", async (t) => { const clock = sinon.useFakeTimers(); let asyncCalled = false; let syncResult = ""; setTimeout(() => { asyncCalled = true; }, 100); // tickAsync breaks the event loop, allowing promises to resolve await clock.tickAsync(100); t.ok(asyncCalled, "async callback should have fired"); clock.restore(); t.end(); } ); ``` --- --- url: /guides/how-to.md description: >- Practical how-to guides for common Sinon.JS scenarios — stubbing dependencies, fake timers with async, TypeScript + SWC, and more. --- # How-to articles Practical guides for common testing scenarios. * [Async functions with fake timers](./fake-timers-async) — Speed up tests that depend on timers * [Link seams (CommonJS)](./link-seams-commonjs) — Isolate your system under test with proxyquire * [Stub a dependency](./stub-dependency) — Stub a dependency of a CommonJS module * [Stub ES module imports](./stub-esm) — Make ES module namespaces mutable for stubbing * [TypeScript and SWC](./typescript-swc) — A detailed case study on real world dependency stubbing --- --- url: /guides/how-to/fake-timers-async.md --- # How to test async functions with fake timers With fake timers, testing code that depends on timers is easier, as it sometimes becomes possible to skip the waiting part and trigger scheduled callbacks synchronously. Consider the following function of a maker module: ```js // maker.js module.exports.callAfterOneSecond = (callback) => { setTimeout(callback, 1000); }; ``` We can use a test runner with Sinon's fake timers to verify that `callAfterOneSecond` works as expected, but skipping that part where the test takes one second: ```js // test.js before(function () { this.clock = sinon.useFakeTimers(); }); after(function () { this.clock.restore(); }); it("should call after one second", function () { const spy = sinon.spy(); maker.callAfterOneSecond(spy); // callback is not called immediately assert.ok(!spy.called); // but it is called synchronously after the clock is fast forwarded this.clock.tick(1000); assert.ok(spy.called); // PASS }); ``` The same approach can be used to test a function returning a promise: ```js module.exports.fulfillAfterOneSecond = () => { return new Promise((resolve) => { setTimeout(() => resolve(42), 1000); }); }; ``` The following test uses a test runner's support for promises: ```js it("should be fulfilled after one second", function () { const promise = maker.fulfillAfterOneSecond(); this.clock.tick(1000); return promise.then((result) => assert.equal(result, 42)); // PASS }); ``` While returning a promise from the test, we can still progress the timers using fake timers, so the test passes almost instantly, and not in 1 second. Since `async` functions behave the same way as functions that return promises explicitly, the following code can be tested using the same approach: ```js // maker.js module.exports.asyncReturnAfterOneSecond = async () => { const setTimeoutPromise = (timeout) => { return new Promise((resolve) => setTimeout(resolve, timeout)); }; await setTimeoutPromise(1000); return 42; }; ``` ```js it("should return 42 after 1000ms", async function () { const promise = maker.asyncReturnAfterOneSecond(); this.clock.tick(1000); const result = await promise; assert.equal(result, 42); // PASS }); ``` Although these tests pass almost instantly, they are still asynchronous. Note that they return promises instead of running the assertions right after the `clock.tick(1000)` call, like in the first example. **Promises' `then()` function always runs asynchronously**, but we can still speed up the tests. --- --- url: /guides/how-to/link-seams-commonjs.md --- # How to stub out CommonJS modules This page describes how to isolate your system under test, by targeting the [link seams](http://www.informit.com/articles/article.aspx?p=359417); replacing your dependencies with stubs you control. > If you want a better understanding of the example and get a good description of what *seams* are, we recommend that you read the [seams (all 3 web pages)](http://www.informit.com/articles/article.aspx?p=359417) excerpt from the classic [Working Effectively with Legacy Code](https://www.goodreads.com/book/show/44919.Working_Effectively_with_Legacy_Code), though it is not strictly necessary. This guide targets the CommonJS module system, made popular by Node.js. There are other module systems, but until recent years this was the de-facto module system and even when the actual EcmaScript Module standard arrived in 2015, transpilers and bundlers can still *output* code as CJS modules. For instance, TypeScript outputs CJS modules per default as of 2023, so it is still relevant, as your `import foo from './foo'` might still end up being transpiled into `const foo = require('./foo')` in the end. For ES Modules (ESM) see [How to stub ES module imports](./stub-esm). ## Hooking into `require` For us to replace the underlying calls done by `require` we need a tool to hook into the process. There are many tools that can do this: rewire, proxyquire, [Quibble](https://www.npmjs.com/package/quibble), etc. This example will be using [proxyquire](https://github.com/thlorenz/proxyquire) to construct our *seams* (i.e. replace the modules), but the actual mechanics will be very similar for the other tools. ## Example The folder structure in our example looks like this: ``` . ├── lib │ └── does-file-exist.js └── test └── does-file-exist.test.js ``` [Source and runnable demo of the example code](https://github.com/sinonjs/demo-proxyquire). ### Source file: `lib/does-file-exist.js` This is the source file of the module `doesFileExist`, it only has one dependency: `fs`. ```javascript var fs = require("fs"); function doesFileExist(path) { return fs.existsSync(path); } module.exports = doesFileExist; ``` ### Test file: `test/does-file-exist.test.js` In order to isolate our `doesFileExist` module for testing, we will stub out `fs` and provide a fake implementation of `fs.existsSync`, where we have complete control of the behaviour. ```javascript var proxyquire = require("proxyquire"); var sinon = require("sinon"); var assert = require("referee").assert; var doesFileExist; // the module to test var existsSyncStub; // the fake method on the dependency describe("example", function () { beforeEach(function () { existsSyncStub = sinon.stub(); // create a stub for every test // import the module to test, using a fake dependency doesFileExist = proxyquire("../lib/does-file-exist", { fs: { existsSync: existsSyncStub, }, }); }); describe("when a path exists", function () { beforeEach(function () { existsSyncStub.returns(true); // set the return value that we want }); it("should return `true`", function () { var actual = doesFileExist("9d7af804-4719-4578-ba1d-5dd8a4dae89f"); assert.isTrue(actual); }); }); }); ``` --- --- url: /guides/how-to/stub-dependency.md --- # How to stub a dependency of a module Sinon is a stubbing library, not a module interception library. Stubbing dependencies is highly dependent on your environment and the implementation. For Node environments, we usually recommend solutions targeting [link seams](./link-seams-commonjs) or explicit dependency injection. Though in some more basic cases, you can get away with only using Sinon by modifying the module exports of the dependency. To stub a dependency (imported module) of a module under test you have to import it explicitly in your test and stub the desired method. For the stubbing to work, the stubbed method cannot be [destructured](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment), neither in the module under test nor in the test. ## An example ### Source file: `dependencyModule.js` ```javascript function getSecretNumber() { return 44; } module.exports = { getSecretNumber, }; ``` ### Source file: `moduleUnderTest.js` ```javascript const dependencyModule = require("./dependencyModule"); function getTheSecret() { return `The secret was: ${dependencyModule.getSecretNumber()}`; } module.exports = { getTheSecret, }; ``` ### Test file: `test.js` ```javascript const assert = require("assert"); const sinon = require("sinon"); const dependencyModule = require("./dependencyModule"); const { getTheSecret } = require("./moduleUnderTest"); describe("moduleUnderTest", function () { describe("when the secret is 3", function () { it("should be returned with a string prefix", function () { sinon.stub(dependencyModule, "getSecretNumber").returns(3); const result = getTheSecret(); assert.equal(result, "The secret was: 3"); }); }); }); ``` ## A complex example with asynchronous code In some cases you might need to stub a dependency that returns a [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). To test it you can add the [async](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) keyword to the test method and call the method being tested with the [await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await) keyword. ### Source file: `userApi.js` ```javascript const axios = require("axios"); async function getPageOfUsers(page) { const result = await axios({ method: "GET", url: `https://reqres.in/api/users?page=${page}`, }); return result.data; } module.exports = { getPageOfUsers, }; ``` ### Source file: `userUtils.js` ```javascript const userApi = require("./userApi"); async function getAllUsers() { const users = []; let page = 0, usersPage = null; do { page += 1; usersPage = await userApi.getPageOfUsers(page); users.push(...usersPage.data); } while (usersPage.total_pages > page); return users; } module.exports = { getAllUsers, }; ``` ### Test file: `UserUtils-test.js` ```javascript const assert = require("assert"); const sinon = require("sinon"); const userUtils = require("./userUtils"); const userApi = require("./userApi"); function aUser(id) { return { id, email: `someemail@user${id}.com`, first_name: `firstName${id}`, last_name: `lastName${id}`, avatar: `https://www.somepage${id}.com`, }; } describe("userUtils", function () { let getPageOfUsersStub; beforeEach(function () { getPageOfUsersStub = sinon.stub(userApi, "getPageOfUsers"); }); afterEach(function () { getPageOfUsersStub.restore(); }); describe("when a single page of users exists", function () { it("should return users from that page", async function () { // Arrange const pageOfUsers = { page: 1, total_pages: 1, data: [aUser(1), aUser(2), aUser(3)], }; getPageOfUsersStub.returns(Promise.resolve(pageOfUsers)); // Act const result = await userUtils.getAllUsers(); // Assert assert.equal(result.length, 3); assert.equal(getPageOfUsersStub.calledOnce, true); }); }); describe("when multiple pages of users exists", function () { it("should return a combined list of all users", async function () { // Arrange const pageOfUsers1 = { page: 1, total_pages: 2, data: [aUser(1), aUser(2), aUser(3)], }; const pageOfUsers2 = { page: 2, total_pages: 2, data: [aUser(4), aUser(5)], }; getPageOfUsersStub.withArgs(1).returns(Promise.resolve(pageOfUsers1)); getPageOfUsersStub.withArgs(2).returns(Promise.resolve(pageOfUsers2)); // Act const result = await userUtils.getAllUsers(); // Assert assert.equal(result.length, 5); assert.equal(getPageOfUsersStub.callCount, 2); }); }); }); ``` --- --- url: /guides/how-to/stub-esm.md --- # How to stub ES module imports ES Modules (ESM) are statically analyzed and their bindings are **live and immutable** by the [ECMAScript specification](https://tc39.es/ecma262/#sec-module-namespace-objects). This means that attempting to stub a named export of an ES module with Sinon will throw a `TypeError` like: ``` TypeError: ES Modules cannot be stubbed ``` This article shows how to configure Node.js to allow mutable ES module namespaces, enabling Sinon stubs to work in an ESM context. ## The problem Consider an ES module source file and a consumer that imports from it: ### Source file: `src/math.mjs` ```javascript export function add(a, b) { return a + b; } ``` ### Module under test: `src/calculator.mjs` ```javascript import { add } from "./math.mjs"; export function calculate(a, b) { return add(a, b); } ``` ### Test file: `test/calculator.test.mjs` ```javascript import sinon from "sinon"; import * as mathModule from "../src/math.mjs"; import { calculate } from "../src/calculator.mjs"; describe("calculator", () => { it("should use the add function", () => { // This will throw: TypeError: ES Modules cannot be stubbed sinon.stub(mathModule, "add").returns(99); }); }); ``` Sinon correctly raises an error here because, per the ES module spec, namespace object properties are non-writable, non-configurable, and non-deletable. ## The solution: use the `esm` package with `mutableNamespace` The [`esm`](https://github.com/standard-things/esm) package is a fast, production-ready ES module loader for Node.js. It offers a `mutableNamespace` option that makes module namespace objects writable, which is what Sinon needs to install stubs. ### Step 1: Install the `esm` package ```bash npm install --save-dev esm ``` ### Step 2: Create a loader / setup file Create a file at the root of your project (e.g., `esm-loader.cjs`) that enables the `mutableNamespace` option: ```javascript // esm-loader.cjs require = require("esm")(module, { cjs: true, mutableNamespace: true, }); ``` > **Note:** The `.cjs` extension (or `"type": "module"` absent in `package.json`) ensures this file is treated as CommonJS, which is required to call `require('esm')`. ### Step 3: Register the loader when running tests Update your `package.json` test script to use `--require` to load the setup file before your test runner: ```json { "scripts": { "test": "mocha --require ./esm-loader.cjs 'test/**/*.test.mjs'" } } ``` ### Step 4: Write the test Now your test can use `sinon.stub()` normally against ES module exports: ```javascript // test/calculator.test.mjs import sinon from "sinon"; import * as mathModule from "../src/math.mjs"; import { calculate } from "../src/calculator.mjs"; import assert from "assert"; describe("calculator", () => { afterEach(() => { sinon.restore(); }); it("should delegate to the add function", () => { sinon.stub(mathModule, "add").returns(99); const result = calculate(1, 2); assert.equal(result, 99); assert.ok(mathModule.add.calledOnce); }); }); ``` ## Complete example: project layout ``` . ├── src │ ├── math.mjs │ └── calculator.mjs ├── test │ └── calculator.test.mjs ├── esm-loader.cjs └── package.json ``` ### `package.json` ```json { "name": "esm-sinon-example", "version": "1.0.0", "scripts": { "test": "mocha --require ./esm-loader.cjs 'test/**/*.test.mjs'" }, "devDependencies": { "esm": "^3.2.25", "mocha": "^10.0.0", "sinon": "*" } } ``` ### `esm-loader.cjs` ```javascript require = require("esm")(module, { cjs: true, mutableNamespace: true, }); ``` ### `src/math.mjs` ```javascript export function add(a, b) { return a + b; } ``` ### `src/calculator.mjs` ```javascript import { add } from "./math.mjs"; export function calculate(a, b) { return add(a, b); } ``` ### `test/calculator.test.mjs` ```javascript import sinon from "sinon"; import * as mathModule from "../src/math.mjs"; import { calculate } from "../src/calculator.mjs"; import assert from "assert"; describe("calculator", () => { afterEach(() => { sinon.restore(); }); it("should use stubbed add function", () => { sinon.stub(mathModule, "add").returns(42); const result = calculate(10, 20); assert.equal(result, 42); assert.ok(mathModule.add.calledOnceWith(10, 20)); }); it("should call the real add function when not stubbed", () => { const result = calculate(3, 4); assert.equal(result, 7); }); }); ``` ## Why does this work? The `esm` package hooks into Node.js's module loading system. When `mutableNamespace: true` is set, it wraps ES module namespace objects with a `Proxy` that allows property assignment. Sinon's `stub()` function replaces the property on the namespace object; with the proxy in place, this assignment succeeds instead of throwing. ## Limitations and caveats * **Only works with the `esm` package.** Native `--experimental-vm-modules` or other loaders do not support `mutableNamespace` out of the box. * **Transpiled output**: If you are using TypeScript or Babel that already compiles your ESM to CommonJS, this approach is not needed. [Stub the CommonJS dependency](./stub-dependency) instead. * **Destructured imports cannot be stubbed.** If the module under test does `import { add } from './math.mjs'` and uses `add` as a local binding, the stub on the namespace will **not** affect the already-captured binding. The consumer must access the export through the module namespace object for stubs to take effect. * **`mutableNamespace` is non-standard.** It deviates from the ESM specification. Consider it a testing convenience rather than a production technique. ## Related articles * [How to stub a dependency of a module (CommonJS)](./stub-dependency) * [How to stub out CommonJS modules using link seams](./link-seams-commonjs) * [Real world dependency stubbing](./typescript-swc) (using TypeScript and SWC) --- --- url: /guides/how-to/typescript-swc.md --- # Case study: real world dependency stubbing Sinon is a simple tool that only tries to do a few things and do them well: creating and injecting test doubles (spies, fakes, stubs) into objects. Unfortunately, in today's world of build pipelines, complex tooling, transpilers and different module systems, doing the simple thing quickly becomes difficult. This article is a detailed step-by-step guide on how one can approach the typical issues that arise and various approaches for debugging and solving them. The real-world case chosen is using Sinon along with [SWC](https://swc.rs/), running tests written in TypeScript in the [Mocha test runner](https://mochajs.org/) and wanting to replace dependencies in this system under test. The essence is that there are always *many* approaches for achieving what you want. Some require tooling, some can get away with almost no tooling, some are general in nature (not specific to SWC for instance) and some are a blend. This means you can usually make some of these approaches work for other combinations of tooling as well, once you understand what is going on. Draw inspiration from the approach and figure out what works for you! ## On TypeScript The Sinon project does not explicitly list TypeScript as a supported target environment. That does not mean Sinon will not run, just that there are so many complications that we cannot come up with guides on figuring out the details for you on every system. TypeScript is a super-set of JavaScript and can be transpiled in a wide variety of ways into JavaScript, both by targeting different runtimes (ES5, ES2015, ES2023, etc) and module systems (CommonJS, ESM, AMD, ...). Some transpilers are closer to what the standard TypeScript compiler produces, some are laxer in various ways and additionally they have all kinds of options to tweak the result. This is indeed complex, so before you dig yourself down in this matter, it is essential that you try to figure out what the resulting code *actually* looks like. As you will see in this guide, adding a few sprinkles of `console.log` with the output of [`Object.getOwnPropertyDescriptor(object, propname)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor) is usually sufficient to understand what is going on! All code and working setups described in this guide are on [GitHub](https://github.com/fatso83/sinon-swc-bug) and links to the correct branch can be found in each section. ## Scenario ### Tech * Mocha: drives the tests * SWC: very fast Rust-based transpiler able to target different module systems (CJS, ESM, ...) and target runtimes (ES5, ES2020, ...) * TypeScript: Type-safe JavaScript superset * Sinon: library for creating and injecting test doubles (stubs, mocks, spies and fakes) * Module system: CommonJS ### Desired outcome Being able to replace exports on the dependency `./other` with a Sinon created test double in `main.ts` when running tests (see code below). ### Problem Running tests with `ts-node` works fine, but changing the setup to using SWC instead results in the tests failing with the following output from Mocha: ``` 1) main should mock: TypeError: Descriptor for property toBeMocked is non-configurable and non-writable ``` ### Original code **`main.ts`** ```typescript import { toBeMocked } from "./other"; export function main() { const out = toBeMocked(); console.log(out); } ``` **`other.ts`** ```typescript export function toBeMocked() { return "I am the original function"; } ``` **`main.spec.ts`** ```typescript import sinon from "sinon"; import "./init"; import * as Other from "./other"; import { main } from "./main"; import { expect } from "chai"; const sandbox = sinon.createSandbox(); describe("main", () => { let mocked; it("should mock", () => { mocked = sandbox.stub(Other, "toBeMocked").returns("mocked"); main(); expect(mocked.called).to.be.true; }); }); ``` Additionally, both the `.swcrc` file used by SWC and the `tsconfig.json` file used by `ts-node` is configured to produce modules of the CommonJS form, not ES Modules. ### Brief Analysis The error message indicates the resulting output of transpilation is different from that of `ts-node`, as this is Sinon telling us that it is unable to do anything with the property of an object, when the [property descriptor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) is essentially immutable. Let us sprinkle some debugging statements to figure out what the differences between the two tools are. First we will add some debugging output to the beginning of the test: ```javascript console.log("Other", Other); console.log( "Other property descriptors", Object.getOwnPropertyDescriptors(Other), ); ``` #### Output of a SWC configured run ``` Other { toBeMocked: [Getter] } Other property descriptors { __esModule: { value: true, writable: false, enumerable: false, configurable: false }, toBeMocked: { get: [Function: get], set: undefined, enumerable: true, configurable: false } } 1) should mock ``` #### Output of a `ts-node` configured run ``` Other { toBeMocked: [Function: toBeMocked] } Other property descriptors { __esModule: { value: true, writable: false, enumerable: false, configurable: false }, toBeMocked: { value: [Function: toBeMocked], writable: true, enumerable: true, configurable: true } } mocked ✔ should mock ``` The important difference is that the property `toBeMocked` is a simple writable *value* in the case of `ts-node` and a non-configurable *getter* in the case of SWC. It being a getter is not a problem for Sinon, as we have a multitude of options for replacing those, but if `configurable` is set to `false` Sinon cannot really do anything about it. #### Conclusion of analysis SWC transforms imports on the form `import * as Other from './other'` into objects where the individual exports are exposed through immutable accessors (*getters*). We can address this issue in mainly 3 ways: 1. reconfigure SWC to produce different output when running tests, either making writable values or configurable getters 2. use pure dependency injection, opening up `./other.ts` to be changed from the inside 3. address how modules are loaded, injecting an additional `require` hook ## Solutions ### Mutating the output from the transpiler If we can just flip the `configurable` flag to `true` during transpilation, Sinon could be instructed to replace the getter. It turns out, there is a SWC *plugin* that does just that: [`swc_mut_cjs_exports`](https://www.npmjs.com/package/swc_mut_cjs_exports). By installing that and adding the following under the `jsc` key in `.swcrc`, you now get a configurable property descriptor. ```json "experimental": { "plugins": [[ "swc_mut_cjs_exports", {} ]] }, ``` A getter *is* different from a value, so you need to change your test code slightly to replace the getter: ```js const stub = sandbox.fake.returns("mocked"); sandbox.replaceGetter(Other, "toBeMocked", () => stub); ``` ### Use pure dependency injection #### Version 1: full manual mode This technique works regardless of language, module systems, bundlers and tool chains, but requires slight modifications of the system under test to allow modifying it. Sinon does not help in resetting state automatically in this scenario. **`other.ts`** ```typescript function _toBeMocked() { return "I am the original function"; } export let toBeMocked = _toBeMocked; export function _setToBeMocked(mockImplementation) { toBeMocked = mockImplementation; } ``` **`main.spec.ts`** ```typescript describe("main", () => { let mocked; let original = Other.toBeMocked; after(() => Other._setToBeMocked(original)); it("should mock", () => { mocked = sandbox.stub().returns("mocked"); Other._setToBeMocked(mocked); main(); expect(mocked.called).to.be.true; }); }); ``` #### Version 2: using Sinon's auto-cleanup This is a slight variation of the fully manual dependency injection version, but with a twist to make it nicer. Sinon 16.1 gained the ability to assign and restore values that were defined using *accessors*. That means, that if you expose an object with setters and getters for props you would like to replace, you can get Sinon to clean up after you. **`other.ts`** ```typescript function _toBeMocked() { return "I am the original function"; } export let toBeMocked = _toBeMocked; export const myMocks = { set toBeMocked(mockImplementation) { toBeMocked = mockImplementation; }, get toBeMocked() { return _toBeMocked; }, }; ``` **`main.spec.ts`** ```typescript describe("main", () => { after(() => sandbox.restore()); it("should mock", () => { mocked = sandbox.fake.returns("mocked"); sandbox.replace.usingAccessor(Other.myMocks, 'toBeMocked', mocked); main(); expect(mocked.called).to.be.true; }); }); ``` ### Hooking into Node's module loading This is what [the article on targeting the link seams](./link-seams-commonjs) is about. The only difference here is using Quibble instead of Proxyquire. Quibble is slightly terser and also supports being used as an ESM *loader*, making it a bit more modern and useful. The end result looks like this: ```typescript describe("main module", () => { let mocked, main; before(() => { mocked = sandbox.stub().returns("mocked"); quibble("./other", { toBeMocked: mocked }); ({ main } = require("./main")); }); it("should mock", () => { main(); expect(mocked.called).to.be.true; }); }); ``` ## Final remarks As can be seen, there are lots of different paths to walk in order to achieve the same basic goal. Find the one that works for your case. --- --- url: /guides/faq.md --- # Frequently Asked Questions ## Property Descriptor Errors ### "Descriptor for property X is non-configurable and non-writable" If you encounter an error like this: ``` TypeError: Descriptor for property toBeMocked is non-configurable and non-writable ``` This error occurs when Sinon tries to stub or spy on a property that has been defined as immutable by JavaScript's property descriptor system. This is not a bug in Sinon, but rather a limitation imposed by the JavaScript engine itself. #### Common Causes 1. **ES Module transpilation**: When ES modules are transpiled to CommonJS (e.g., by TypeScript, Babel, or SWC), the exported properties often become non-configurable and non-writable. 2. **`Object.freeze()` or `Object.seal()`**: Objects that have been frozen or sealed have immutable properties. 3. **Native browser/Node.js APIs**: Some built-in objects and their properties are inherently immutable. 4. **Third-party libraries**: Some libraries define their exports with non-configurable descriptors. #### Solutions 1. **Use dependency injection**: Instead of stubbing the import directly, pass the dependency as a parameter: ```javascript // Instead of this: import { toBeMocked } from "./module"; sinon.stub(module, "toBeMocked"); // This might fail // Do this: function myFunction(dependency = toBeMocked) { return dependency(); } // In tests: const stub = sinon.stub(); myFunction(stub); ``` 2. **Stub at the module level**: For ES modules, consider using a tool like `proxyquire` or `testdouble.js` for module-level mocking. 3. **Use dynamic imports**: Dynamic imports can sometimes work around transpilation issues: ```javascript // In your test const module = await import("./module"); sinon.stub(module, "toBeMocked"); ``` 4. **Restructure your code**: Consider whether the code under test can be refactored to be more testable. #### For TypeScript Users When using TypeScript with SWC or similar transpilers, see our [TypeScript with SWC guide](./how-to/typescript-swc) for specific solutions. #### Further Reading * [MDN: Object.getOwnPropertyDescriptor()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor) * [MDN: Property descriptors](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#Description) * [How-to: Stub dependencies in CommonJS](./how-to/stub-dependency) --- --- url: /guides/migration.md --- # Migrating between versions ## Sinon 19 ### All timers are stubbed by default A breaking change is that Fake Timers 13 now fake all timers by default. Previously Node's `nextTick()` and `Window#queueMicroTask()` were explicitly skipped, which was quite confusing to some. This typically might affect `async` tests where you rely on some Node function that invokes `nextTick` under the hood. See [issue #2619](https://github.com/sinonjs/sinon/issues/2619) for such an example and [a suggestion on how one could employ `clock#runToLastAsync()`](https://github.com/fatso83/usefaketimers-bug-reproduction/commit/54c812d) in asynchronous tests that stopped resolving in Sinon 19. *If you want the old behavior, specify the timers you want to fake in the `toFake` option and leave out the name of the timers giving you trouble*. ### New implementation of the fake Date class The new version of fake-timers also no longer creating dates using the original Date class, but a *subclass* (proxy). This should not matter *unless* you are doing some kind of identity checks on the constructor: functionally they are the same. See ([fake-timers#504](https://github.com/sinonjs/fake-timers/issues/504)) for an example (which we ended up pushing a small fix for to make `instanceof` work as before). ### Removal of `useFakeServer({legacyRoutes: true})` The `legacyRoutes` option that was enabled in a previous version has been removed. An underlying library, `path-to-regexp`, had a fundamental change to its parsing that made the option a no-op. This should not affect most users of Sinon. ## Sinon 18 Mostly removal of some deprecated exports related to `sinon-test`, such as `sinon.defaultConfig` and related modules. ## Sinon 17 Drops support for Node 16. ## Sinon 15 Removes option to pass a custom formatter. ## Sinon 7 For users upgrading to Sinon 7 the only known breaking API change is that **negative ticks** are not allowed in `sinon@7` due to updating to lolex 3 internally. This means you cannot use negative values in `sinon.useFakeTimers().tick()`. If you experience any issues moving from Sinon 6 to Sinon 7, please [let us know](https://github.com/sinonjs/sinon/issues/new?template=Bug_report.md). ## Sinon 6 There should be no reason for any code changes with the new Sinon 6. Usually, `MAJOR` releases should come with breaking changes, but there are no known breaking changes in `sinon@6` at the time of this writing. We chose to release a new major version as a [pragmatic solution to some noise related to releasing Sinon 5.1](https://github.com/sinonjs/sinon/pull/1829#issue-193284761), which featured some breaking changes related to ESM (which since has been resolved). If you should experience any issues moving from Sinon 5 to Sinon 6, please [let us know](https://github.com/sinonjs/sinon/issues/new?template=Bug_report.md). ## Sinon 5 As with all `MAJOR` releases in [semver](http://semver.org/), there are breaking changes in `sinon@5`. This guide will walk you through those changes. ### `spy.reset()` is removed, use `spy.resetHistory()` In a previous version we deprecated and aliased `spy.reset` in favour of using `spy.resetHistory`. `spy.reset` has now been removed, you should use `spy.resetHistory`. ### `sinon` is now a (default) sandbox Since `sinon@5.0.0`, the `sinon` object is a default sandbox. Unless you have a very advanced setup or need a special configuration, you probably want to only use that one. The old sandbox API is still available, so you don't **have** to do anything. However, switching to using the default sandbox can help make your code more concise. **Before (`sinon@4`)**: ```js describe("myFunction", function () { var sandbox = sinon.sandbox.create(); afterEach(function () { sandbox.restore(); }); it("should make pie"); }); ``` **After (`sinon@5`)**: ```js describe("myFunction", function () { afterEach(function () { sinon.restore(); }); it("should make pie"); }); ``` ## Sinon 4 As with all `MAJOR` releases in [semver](http://semver.org/), there are breaking changes in `sinon@4`. This guide will walk you through those changes. ### `sinon.stub(obj, 'nonExistingProperty')` — Throws Trying to stub a non-existing property will now fail, to ensure you are creating [less error-prone tests](https://github.com/sinonjs/sinon/pull/1557). ## Sinon 3 As with all `MAJOR` releases in [semver](http://semver.org/), there are breaking changes in `sinon@3`. This guide will walk you through those changes. ### `sinon.stub(object, "method", func)` — Removed Please use `sinon.stub(obj, "method").callsFake(func)` instead. ```js var stub = sinon.stub(obj, "stubbedMethod").callsFake(function () { return 42; }); ``` A [codemod is available](https://github.com/hurrymaplelad/sinon-codemod) to upgrade your code. ### `sinon.stub(object, property, value)` — Removed Calling `sinon.stub` with three arguments will throw an Error. This was deprecated with `sinon@2` and has been removed with `sinon@3`. ### `sinon.useFakeTimers([now, ]prop1, prop2, ...)` — Removed `sinon.useFakeTimers()` signature has changed. To define which methods to fake, please use `config.toFake`. Other options are now available when configuring `useFakeTimers`. Please consult the documentation for more information. ### `sinon.sandbox.create(config)` — Config changes The changes in configuration for fake timers implicitly affect sandbox creation. If your config used to look like `{ useFaketimers: ["setTimeout", "setInterval"]}`, you will now need to change it to `{ useFaketimers: { toFake: ["setTimeout", "setInterval"] }}`. ### `sandbox.stub(obj, 'nonExistingProperty')` — Throws Trying to stub a non-existing property will now fail to ensure you are creating [less error-prone tests](https://github.com/sinonjs/sinon/issues/1537#issuecomment-323948482). ### Removal of internal helpers The following internal functions were deprecated as of `sinon@1.x` and have been removed in `sinon@3`: * `sinon.calledInOrder` * `sinon.create` * `sinon.deepEqual` * `sinon.format` * `sinon.functionName` * `sinon.functionToString` * `sinon.getConfig` * `sinon.getPropertyDescriptor` * `sinon.objectKeys` * `sinon.orderByFirstCall` * `sinon.restore` * `sinon.timesInWorlds` * `sinon.valueToString` * `sinon.walk` * `sinon.wrapMethod` * `sinon.Event` * `sinon.CustomEvent` * `sinon.EventTarget` * `sinon.ProgressEvent` * `sinon.typeOf` * `sinon.extend` ## Sinon 2 Sinon v2.0 is the second major release, we have made several breaking changes in this release as a result of modernising the internals of Sinon. This guide is intended to walk you through the changes. ### `sinon.log` and `sinon.logError` Removed `sinon.log` and `sinon.logError` were used in Sinon v1.x to globally configure `FakeServer`, `FakeXMLHttpRequest` and `FakeXDomainRequest`; these three functions now allow the logger to be configured on a per-use basis. In v1.x you may have written: ```js sinon.log = function (msg) { /* your logging impl */ }; ``` You would now individually import and configure the utility upon creation: ```js var sinon = require("sinon"); var myFakeServer = sinon.fakeServer.create({ logger: function (msg) { /* your logging impl */ } }); ``` ### `sinon.test`, `sinon.testCase` and `sinon.config` Removed `sinon.test` and `sinon.testCase` have been extracted from the Sinon API and moved into their own node module, [sinon-test](https://www.npmjs.com/package/sinon-test). Please refer to the [sinon-test README](https://github.com/sinonjs/sinon-test/blob/master/README.md) for migration examples. ### `stub.callsFake` replaces `stub(obj, 'meth', fn)` `sinon.stub(obj, 'meth', fn)` return a spy, not a full stub. Behavior could not be redefined. `stub.callsFake` now returns a full stub. Here's a [codemod script](https://github.com/hurrymaplelad/sinon-codemod) to help you migrate. See [discussion](https://github.com/sinonjs/sinon/pull/823). ```js // Old sinon.stub(obj, "meth", fn); // New sinon.stub(obj, "meth").callsFake(fn); ``` ### `stub.resetHistory` replaces `stub.reset` `stub.reset()` now resets the history and the behaviour of the stub. Previously `stub.reset()` only reset the history of the stub. Stubs now have separate methods for resetting the history and the behaviour. To mimic the old behaviour replace all `stub.reset()` calls with `stub.resetHistory()`. ```js // Old stub.reset(); // New stub.resetHistory(); ``` ### Deprecation of internal helpers The following utility functions are being marked as deprecated and are planned for removal in Sinon v3.0; please check your codebase for usage to ease future migrations: * `sinon.calledInOrder` * `sinon.create` * `sinon.deepEqual` * `sinon.format` * `sinon.functionName` * `sinon.functionToString` * `sinon.getConfig` * `sinon.getPropertyDescriptor` * `sinon.objectKeys` * `sinon.orderByFirstCall` * `sinon.restore` * `sinon.timesInWorlds` * `sinon.valueToString` * `sinon.walk` * `sinon.wrapMethod` * `sinon.Event` * `sinon.CustomEvent` * `sinon.EventTarget` * `sinon.ProgressEvent` * `sinon.typeOf` * `sinon.extend` ### `sandbox.useFakeXMLHttpRequest` no longer returns a "server" In Sinon 1.x, the sandbox' `useFakeXMLHttpRequest` was the same as its `useFakeServer`. In 2.x, it maps directly to `sinon.useFakeXMLHttpRequest` (but with sandboxing). If you use `sandbox.useFakeXMLHttpRequest`, replace it with `sandbox.useFakeServer`, and your tests should behave as they always did. ### `sinon.behavior` is gone The `sinon.behavior` object is no longer exposed for random modification. However, there is a new mechanism in place aided to add new behavior to stubs, `sinon.addBehavior(name, fn)`, see the stub docs. --- --- url: /guides/external-resources.md description: Curated list of external articles and related libraries for Sinon.JS. --- # External resources ## Articles elsewhere on the web * [How to stub or mock complex objects, such as DOM objects](https://codeutopia.net/blog/2016/05/23/sinon-js-quick-tip-how-to-stubmock-complex-objects-such-as-dom-objects/) * [Using Sinon.js with Promises](https://www.sitepoint.com/promises-in-javascript-unit-tests-the-definitive-guide/) * [Best practices for spies, stubs and mocks](https://semaphoreci.com/community/tutorials/best-practices-for-spies-stubs-and-mocks-in-sinon-js) * [Using Sinon.js to help test Mongoose models](https://codeutopia.net/blog/2016/06/10/mongoose-models-and-unit-tests-the-definitive-guide/) * [Stubbing HTTP Requests With Sinon](http://mherman.org/blog/2017/11/06/stubbing-http-requests-with-sinon/) * [Stubbing Node Authentication Middleware with Sinon](http://mherman.org/blog/2018/01/22/stubbing-node-authentication-middleware-with-sinon) * [Testing ImmutableJS with Sinon custom matchers](https://www.scraggo.com/testing-immutable-js-with-sinon-custom-matchers/) * [SinonJS Fundamentals](https://www.pluralsight.com/courses/sinonjs-fundamentals) (Pluralsight course) ## Related libraries * [proxyquire](https://github.com/thlorenz/proxyquire) — Proxies Node.js require to override dependencies during testing * [inject-loader](https://github.com/plasticine/inject-loader) — Webpack loader that allows overriding dependencies during testing * [fetch-mock](http://www.wheresrhys.co.uk/fetch-mock/) — Mock HTTP requests made using fetch * [mock-socket](https://github.com/thoov/mock-socket) — Mocking library for WebSockets and socket.io * [testdouble.js](https://github.com/testdouble/testdouble.js) — Minimal test double library for TDD with JavaScript --- --- url: /concepts/utils.md description: >- Internal utilities like createStubInstance and restoreObject. May change without notice. --- # Utilities Sinon.JS has a few utilities used internally in `lib/sinon.js`. Unless the method in question is documented here, it should not be considered part of the public API, and thus is subject to change. ## Utils API ### `sinon.createStubInstance(constructor);` Creates a new object with the given function as the prototype and stubs all implemented functions. ```js import t from "tap"; import sinon from "sinon"; t.test("createStubInstance stubs all implemented functions", (t) => { class Container { contains(item) { /* ... */ } } const stubContainer = sinon.createStubInstance(Container); stubContainer.contains.returns(false); stubContainer.contains.withArgs("item").returns(true); // Verify the stubbed behavior works as configured t.equal( stubContainer.contains("other"), false, "contains should return false by default" ); t.equal( stubContainer.contains("item"), true, "contains should return true for 'item'" ); // Verify it's actually a stub t.ok(stubContainer.contains.calledTwice, "contains should be called twice"); t.type( stubContainer, Container, "stubContainer should be instance of Container" ); t.end(); }); ``` The given constructor function is not invoked. See also the [stub API](/concepts/stubs/). ### `sinon.restoreObject(object);` Restores all methods of an object and returns the restored object. ```js import t from "tap"; import sinon from "sinon"; t.test("restoreObject restores all methods and returns the object", (t) => { const obj = { foo: () => {} }; const originalFoo = obj.foo; // Wrap method with spy sinon.spy(obj, "foo"); // Verify method is now a spy t.ok(obj.foo.restore, "foo should have restore method (is wrapped)"); // Restore the object const result = sinon.restoreObject(obj); // Verify restoration t.equal(result, obj, "should return the restored object"); t.notOk(obj.foo.restore, "foo should no longer have restore method"); t.equal(obj.foo, originalFoo, "foo should be restored to original"); t.end(); }); ``` Does nothing if the object contains no restorable methods (spies, stubs, etc). ```js import t from "tap"; import sinon from "sinon"; t.test("restoreObject is a no-op when object has no restorable methods", (t) => { const emptyObj = {}; t.doesNotThrow( () => sinon.restoreObject(emptyObj), "should be a no-op when object has no restorable methods" ); t.end(); }); ``` Does nothing if the object contains no restorable methods (spies, stubs, etc). --- --- url: /concepts/promises.md description: >- Create fake promises with controllable resolution and rejection for testing async code. --- # Promises `promise` allows to create fake promises that expose their internal state and can be resolved or rejected on demand. ## Creating a promise ```js var promise = sinon.promise(); ``` ### Creating a promise with a fake executor ```js var executor = sinon.fake(); var promise = sinon.promise(executor); ``` ### Creating a promise with custom executor ```js var promise = sinon.promise(function (resolve, reject) { // ... }); ``` ## Promise API ### `promise.status` The internal status of the promise. One of `pending`, `resolved`, `rejected`. ### `promise.resolvedValue` The promise resolved value. ### `promise.rejectedValue` The promise rejected value. ### `promise.resolve(value)` Resolves the promise with the given value. Throws if the promise is not `pending`. ### `promise.reject(value)` Rejects the promise with the given value. Throws if the promise is not `pending`. --- --- url: /concepts/assertions/api/_index.md --- # Assertions API ## Methods * [`alwaysCalledOn`][alwaysCalledOn] * [`alwaysCalledWith`][alwaysCalledWith] * [`alwaysCalledWithExactly`][alwaysCalledWithExactly] * [`alwaysCalledWithMatch`][alwaysCalledWithMatch] * [`alwaysThrew`][alwaysThrew] * [`callCount`][callCount] * [`called`][called] * [`calledOn`][calledOn] * [`calledOnce`][calledOnce] * [`calledOnceWithExactly`][calledOnceWithExactly] * [`calledOnceWithMatch`][calledOnceWithMatch] * [`calledTwice`][calledTwice] * [`calledThrice`][calledThrice] * [`calledWith`][calledWith] * [`calledWithExactly`][calledWithExactly] * [`calledWithMatch`][calledWithMatch] * [`calledWithNew`][calledWithNew] * [`callOrder`][callOrder] * [`expose`][expose] * [`fail`][fail] * [`match`][match] * [`neverCalledWith`][neverCalledWith] * [`neverCalledWithMatch`][neverCalledWithMatch] * [`notCalled`][notCalled] * [`pass`][pass] * [`threw`][threw] [alwaysCalledOn]: ./always-called-on [alwaysCalledWith]: ./always-called-with [alwaysCalledWithExactly]: ./always-called-with-exactly [alwaysCalledWithMatch]: ./always-called-with-match [alwaysThrew]: ./always-threw [callCount]: ./call-count [callOrder]: ./call-order [called]: ./called [calledOn]: ./called-on [calledOnce]: ./called-once [calledOnceWithExactly]: ./called-once-with-exactly [calledOnceWithMatch]: ./called-once-with-match [calledTwice]: ./called-twice [calledThrice]: ./called-thrice [calledWith]: ./called-with [calledWithExactly]: ./called-with-exactly [calledWithMatch]: ./called-with-match [calledWithNew]: ./called-with-new [expose]: ./expose [fail]: ./fail [match]: ./match [neverCalledWith]: ./never-called-with [neverCalledWithMatch]: ./never-called-with-match [notCalled]: ./not-called [pass]: ./pass [threw]: ./threw --- --- url: /concepts/assertions/api.md description: >- Built-in assertion methods for verifying spy, stub, and mock behavior. Provides detailed error messages on failure. --- ## Methods * [`alwaysCalledOn`][alwaysCalledOn] * [`alwaysCalledWith`][alwaysCalledWith] * [`alwaysCalledWithExactly`][alwaysCalledWithExactly] * [`alwaysCalledWithMatch`][alwaysCalledWithMatch] * [`alwaysThrew`][alwaysThrew] * [`callCount`][callCount] * [`called`][called] * [`calledOn`][calledOn] * [`calledOnce`][calledOnce] * [`calledOnceWithExactly`][calledOnceWithExactly] * [`calledOnceWithMatch`][calledOnceWithMatch] * [`calledTwice`][calledTwice] * [`calledThrice`][calledThrice] * [`calledWith`][calledWith] * [`calledWithExactly`][calledWithExactly] * [`calledWithMatch`][calledWithMatch] * [`calledWithNew`][calledWithNew] * [`callOrder`][callOrder] * [`expose`][expose] * [`fail`][fail] * [`match`][match] * [`neverCalledWith`][neverCalledWith] * [`neverCalledWithMatch`][neverCalledWithMatch] * [`notCalled`][notCalled] * [`pass`][pass] * [`threw`][threw] [alwaysCalledOn]: ./always-called-on [alwaysCalledWith]: ./always-called-with [alwaysCalledWithExactly]: ./always-called-with-exactly [alwaysCalledWithMatch]: ./always-called-with-match [alwaysThrew]: ./always-threw [callCount]: ./call-count [callOrder]: ./call-order [called]: ./called [calledOn]: ./called-on [calledOnce]: ./called-once [calledOnceWithExactly]: ./called-once-with-exactly [calledOnceWithMatch]: ./called-once-with-match [calledTwice]: ./called-twice [calledThrice]: ./called-thrice [calledWith]: ./called-with [calledWithExactly]: ./called-with-exactly [calledWithMatch]: ./called-with-match [calledWithNew]: ./called-with-new [expose]: ./expose [fail]: ./fail [match]: ./match [neverCalledWith]: ./never-called-with [neverCalledWithMatch]: ./never-called-with-match [notCalled]: ./not-called [pass]: ./pass [threw]: ./threw --- --- url: /concepts/matchers/combining-matchers.md description: >- Combine multiple matchers using and(), or(), and not() for complex argument matching logic. --- # Combining matchers All matchers implement `and` and `or`. This allows to logically combine multiple matchers. The result is a new matcher that requires both (`and`) or one of the matchers (`or`) to return `true`. ```js import t from "tap"; import sinon from "sinon"; function Book(p) { this.pages = p; } t.test(`combining matchers`, (t) => { const stringOrNumber = sinon.match.string.or(sinon.match.number); const f = sinon.fake(); f("apple pie"); t.ok(f.calledWith(stringOrNumber)); const bookWithPages = sinon.match .instanceOf(Book) .and(sinon.match.has("pages")); const b = new Book(42); const h = sinon.fake(); h(b); t.ok(h.calledWith(bookWithPages)); t.end(); }); ``` --- --- url: /concepts/matchers/custom-matchers.md description: >- Create custom matcher functions using sinon.match() to define flexible matching logic for your tests. --- # Custom matchers Custom matchers are created with the `sinon.match` factory. The test function takes a value as the only argument. It must return `true`, when the value matches the expectation and `false` otherwise. ```js import t from "tap"; import sinon from "sinon"; function test(value) { return Boolean(value); } const trueIsh = sinon.match(test); t.test(`custom matcher`, (t) => { const f = sinon.fake(); f("apple pie"); t.ok(f.calledWith(trueIsh)); t.end(); }); ``` --- --- url: /concepts/mocks/error-handling.md description: >- Mocks throw errors when expectations aren't met or when used incorrectly. Learn about common mock errors and how to fix them. --- # Error Handling in Mocks Mocks have strict verification requirements and will throw errors when expectations aren't met or when used incorrectly. Understanding these errors is essential for effective mock usage. ## Null or Undefined Object Attempting to create a mock for a null or undefined object will fail. ```javascript // This will throw: "object is null" sinon.mock(null); sinon.mock(undefined); ``` **Solution:** Provide a valid object: ```javascript const obj = { method() {} }; const mock = sinon.mock(obj); ``` ## Falsy Method Name Setting an expectation on a falsy method name will fail. ```javascript const mock = sinon.mock(obj); // This will throw: "method is falsy" mock.expects(null); mock.expects(undefined); mock.expects(""); ``` **Solution:** Provide a valid method name: ```javascript mock.expects("validMethodName"); ``` ## Unmet Expectations The most common mock error: calling `verify()` when expectations aren't met. ```javascript const obj = { greet() {} }; const mock = sinon.mock(obj); mock.expects("greet").once(); // greet is never called mock.verify(); // Throws: "Expected greet(...) once (never called)" ``` **Error message includes:** * Which expectation failed * How many times it was expected * How many times it was actually called **Solution:** Ensure all expectations are met: ```javascript const obj = { greet() {} }; const mock = sinon.mock(obj); mock.expects("greet").once(); obj.greet(); // Call the method mock.verify(); // Now succeeds ``` ## Unexpected Calls Calling a mocked method in a way that doesn't match any expectations will fail immediately. ```javascript const obj = { greet(name) {} }; const mock = sinon.mock(obj); mock.expects("greet").withExactArgs("Alice"); // This throws immediately: "Unexpected call: greet(Bob)" obj.greet("Bob"); ``` **Error message includes:** * The unexpected call details * All defined expectations * Stack trace for debugging **Solution:** Either adjust the expectation or fix the call: ```javascript // Option 1: Use withArgs instead of withExactArgs mock.expects("greet").withArgs("Alice"); // Also accepts more args // Option 2: Match the expectation exactly obj.greet("Alice"); // Now matches // Option 3: Add multiple expectations mock.expects("greet").withExactArgs("Alice"); mock.expects("greet").withExactArgs("Bob"); ``` ## Verify Auto-Restores An important behavior: `mock.verify()` automatically calls `mock.restore()`. ```javascript const obj = { method() { return "original"; } }; const mock = sinon.mock(obj); mock.expects("method").once(); obj.method(); mock.verify(); // Verifies AND restores // Method is now restored obj.method(); // Returns 'original', not a mock ``` **This is by design.** The expectation is that verify is your last action with the mock. **Problem:** Calling verify() twice will fail on the second call: ```javascript mock.verify(); // First call succeeds and restores mock.verify(); // Second call throws - mock is already restored ``` **Solution:** Only call `verify()` once per mock, typically in test cleanup: ```javascript afterEach(() => { mock.verify(); // Verify once at the end }); ``` ## Overwriting Expectations Setting multiple expectations with `withArgs()` or `withExactArgs()` on the same expectation will overwrite previous arguments. ```javascript const mock = sinon.mock(obj); const expectation = mock.expects("method"); expectation.withArgs("first"); expectation.withArgs("second"); // Overwrites 'first' obj.method("first"); // Fails! Expectation now requires 'second' ``` **This is documented behavior:** An expectation holds only one set of arguments. **Solution:** Create multiple expectations for different arguments: ```javascript const mock = sinon.mock(obj); mock.expects("method").withArgs("first"); mock.expects("method").withArgs("second"); obj.method("first"); // Matches first expectation obj.method("second"); // Matches second expectation ``` ## Too Many Calls Calling a method more times than expected will fail. ```javascript const mock = sinon.mock(obj); mock.expects("method").once(); obj.method(); // OK obj.method(); // Throws: "Unexpected call: method()" ``` **Solution:** Adjust expectation or use `atLeast()`: ```javascript // Option 1: Set correct count mock.expects("method").twice(); // Option 2: Use atLeast for minimum mock.expects("method").atLeast(1); // Allows 1 or more // Option 3: Use atMost for maximum mock.expects("method").atMost(2); // Allows 0, 1, or 2 ``` ## Debugging Mock Failures When mock expectations fail, the error messages can be dense. Here's how to read them: ```javascript Error: Expected greet('[...]') once (never called) greet(Bob) at MyTest.js:15 ``` **Reading the error:** 1. **"Expected greet('\[...]') once"** - The expectation 2. **"(never called)"** - What actually happened 3. **"greet(Bob) at MyTest.js:15"** - Stack trace of unexpected calls (if any) **Debugging steps:** 1. Check the expectation: Is it correct? 2. Check the actual calls: Are they happening? 3. Check the arguments: Do they match? 4. Check the call count: Right number of calls? ## Best Practices 1. **One mock per test** - Multiple mocks make failures hard to diagnose 2. **Verify once** - Call `verify()` only once, in cleanup 3. **Expect only what matters** - Don't mock what you don't need to verify 4. **Use explicit assertions** - Consider fakes + assertions instead of mocks 5. **Test behavior, not implementation** - Avoid coupling tests to internal calls 6. **Clear error messages** - Use descriptive method names in tests ## Common Pitfalls ### Testing Implementation Details ```javascript // BAD: Testing how the code works internally mock.expects("_privateMethod").once(); mock.expects("helperFunction").twice(); // GOOD: Testing what the code does const fake = sinon.fake.returns("result"); sinon.replace(obj, "publicMethod", fake); // Assert on behavior, not internal calls ``` ### Too Many Expectations ```javascript // BAD: Every interaction is an expectation mock.expects("log").atLeast(1); mock.expects("validateInput").once(); mock.expects("processData").once(); mock.expects("sendResponse").once(); // GOOD: Only mock what you need to control or verify const fake = sinon.fake.resolves("result"); sinon.replace(service, "getData", fake); // Explicit assertions on behavior assert.ok(fake.called); ``` ### Brittle Tests ```javascript // BAD: Test breaks when call order changes mock.expects("a").once(); mock.expects("b").once(); mock.expects("c").once(); // Now code must call in exact order: a, b, c // GOOD: Test the outcome, not the path const result = await service.process(); assert.equal(result, expectedValue); ``` ## See Also * [Mock API Documentation](/concepts/mocks/) * [When NOT to use mocks](./#when-to-not-use-mocks) * [Mocks vs Stubs vs Fakes](./#mocks-vs-stubs-vs-fakes) * [Migrating from Mocks](./migrating-from-mocks) * [Martin Fowler: Mocks Aren't Stubs](https://martinfowler.com/articles/mocksArentStubs.html) --- --- url: /concepts/spies/error-handling.md description: >- Spies validate their usage and throw errors when used incorrectly. Learn about common spy errors and how to fix them. --- # Error Handling in Spies Spies validate their usage and throw errors when used incorrectly. Understanding these errors helps you use spies properly. ## ES Modules Cannot Be Spied Spies cannot be created on ES Modules because their exports are read-only. ```javascript import * as myModule from "./my-module.js"; // This will throw: "ES Modules cannot be spied" sinon.spy(myModule); ``` **Solution:** Use individual exports or CommonJS modules if you need to spy on module exports. ## Yield Methods Called Before Invocation Methods like `yield()`, `yieldOn()`, `yieldTo()`, and `yieldToOn()` can only be called after the spy has been invoked at least once. ```javascript const spy = sinon.spy(); // This will throw: "spy cannot yield since it was not yet invoked" spy.yield(); ``` **Solution:** Call the spy first, then use yield methods on the call object: ```javascript const spy = sinon.spy(); function callback(value) { /* ... */ } spy(callback); spy.getCall(0).yield("value"); // Works! ``` ## CallArg Methods Called Before Invocation Methods like `callArg()`, `callArgWith()`, `callArgOn()`, and `callArgOnWith()` require the spy to have been called first. ```javascript const spy = sinon.spy(); // This will throw: "spy cannot call arg since it was not yet invoked" spy.callArg(0); ``` **Solution:** Call the spy first, then use callArg methods on the call object: ```javascript const spy = sinon.spy(); function callback() { /* ... */ } spy(callback); spy.getCall(0).callArg(0); // Works! ``` ## Best Practices 1. **Check for calls before accessing call data** - Use `spy.called` or `spy.callCount` before accessing `spy.getCall(n)` 2. **Use appropriate spy type** - Choose anonymous spies, method wrappers, or property accessors based on your needs 3. **Restore spies** - Always restore spies with `spy.restore()` to clean up 4. **Consider using fakes** - For new code, `sinon.fake()` provides a simpler API with the same functionality ## See Also * [Spy API Documentation](./api/) * [Migrating to Fakes](./migrating-to-fakes) * [Fakes Error Handling](/concepts/fakes/error-handling) --- --- url: /concepts/stubs/error-handling.md description: >- Stubs validate their usage and throw errors when used incorrectly. Learn about common stub errors and how to fix them. --- # Error Handling in Stubs Stubs validate their usage and throw errors when used incorrectly. Understanding these errors helps you use stubs properly. ## ES Modules Cannot Be Stubbed Stubs cannot be created on ES Modules because their exports are read-only. ```javascript import * as myModule from "./my-module.js"; // This will throw: "ES Modules cannot be stubbed" sinon.stub(myModule, "someMethod"); ``` **Solution:** Use CommonJS modules or stub individual imported functions: ```javascript import { someMethod } from "./my-module.js"; // Create a wrapper object to stub const wrapper = { someMethod }; sinon.stub(wrapper, "someMethod"); ``` Or use `sinon.replace()`: ```javascript import * as myModule from "./my-module.js"; import * as sinon from "sinon"; const fake = sinon.fake.returns("mocked value"); sinon.replace(myModule, "someMethod", fake); ``` ## Removed Three-Argument Form The three-argument form `stub(obj, 'method', func)` has been removed in modern Sinon versions. ```javascript const obj = { method() { return "original"; } }; // This will throw: "stub(obj, 'meth', fn) has been removed, see documentation" sinon.stub(obj, "method", function () { return "replacement"; }); ``` **Solution:** Use `callsFake()` instead: ```javascript const obj = { method() { return "original"; } }; // Correct approach sinon.stub(obj, "method").callsFake(function () { return "replacement"; }); ``` ## Stubbing Non-Existent Properties Attempting to stub a property that doesn't exist will fail. ```javascript const obj = { existingMethod() {} }; // This may throw an error or behave unexpectedly sinon.stub(obj, "nonExistentMethod"); ``` **Solution:** Ensure the property exists first, or use `sinon.replace()`: ```javascript const obj = { existingMethod() {} }; // Add the method first obj.nonExistentMethod = function () {}; sinon.stub(obj, "nonExistentMethod"); // Or use replace which handles non-existent properties sinon.replace(obj, "nonExistentMethod", sinon.stub()); ``` ## Stubbing Non-Function Properties Stubs can only replace functions, not regular properties. ```javascript const obj = { name: "Alice" }; // This will throw an error sinon.stub(obj, "name"); ``` **Solution:** Use `sinon.stub().get()` or `sinon.stub().value()` for properties: ```javascript const obj = { name: "Alice" }; // For getter/setter sinon.stub(obj, "name").get(() => "Bob"); // Or use value() for simple replacement sinon.stub(obj, "name").value("Bob"); ``` ## Restoring Stubs Forgetting to restore stubs can cause test pollution. ```javascript const obj = { method() { return "original"; } }; sinon.stub(obj, "method").returns("stubbed"); // If you don't restore, subsequent tests will see the stub obj.method(); // Still returns 'stubbed' ``` **Solution:** Always restore stubs: ```javascript const obj = { method() { return "original"; } }; const stub = sinon.stub(obj, "method").returns("stubbed"); // Manual restore stub.restore(); // Or use sinon.restore() to restore all stubs sinon.stub(obj, "method").returns("stubbed"); sinon.restore(); // Restores all stubs created via sinon.stub() // Or use test framework hooks afterEach(() => { sinon.restore(); }); ``` ## Behavior Definition After Calls Some behavior changes only affect future calls, not past ones. ```javascript const stub = sinon.stub().returns("first"); stub(); // returns 'first' stub.returns("second"); stub(); // returns 'second', not 'first' // But this doesn't change the recorded call data stub.getCall(0).returnValue; // Still 'first' ``` **This is expected behavior:** Stubs record call information immutably, but their behavior can change for future calls. ## Conflicting Behaviors Using multiple behavior methods can be confusing: ```javascript const stub = sinon.stub().returns("A").returns("B"); stub(); // What does this return? ``` **Result:** Returns 'B' - the last `returns()` call wins. **Solution:** Use `onCall()` for sequential behaviors: ```javascript const stub = sinon .stub() .onFirstCall() .returns("A") .onSecondCall() .returns("B"); stub(); // 'A' stub(); // 'B' ``` ## Best Practices 1. **Always restore** - Use `afterEach(() => sinon.restore())` in tests 2. **Use fakes for simple cases** - Reserve stubs for complex scenarios 3. **Be explicit with behavior** - Use `onCall()` for sequential, `withArgs()` for conditional 4. **Verify stubs exist** - Check property exists before stubbing 5. **Prefer `callsFake()`** - More flexible than old three-argument form 6. **Use property stubbing correctly** - `.get()`, `.set()`, or `.value()` for properties ## See Also * [Stub API Documentation](./api/) * [Migrating to Fakes](./migrating-to-fakes) * [Fakes Error Handling](/concepts/fakes/error-handling) * [Spy Error Handling](/concepts/spies/error-handling) --- --- url: /concepts/fakes/api.md description: >- Create and configure fake functions with returns, throws, yields, and async behavior. --- # Fakes API There are three ways you can create fakes: 1. Create an empty fake 2. Wrap an existing function 3. Use a fake factory ## Create an empty fake A basic fake can be created with no behavior. It saves call information. ## Wrap an existing function You can use `sinon.fake` to wrap an existing function. This can be used for either observing the [system under test][SUT], or for creating complex fakes with behaviour. You can pass an arbitrarily complex function to `sinon.fake` to create custom behavior for a test. ## Use a fake factory The API has a few factories to quickly create fakes with behavior: * [fake.rejects][rejects] * [fake.resolves][resolves] * [fake.returns][returns] * [fake.throws][throws] * [fake.yields][yields] * [fake.yieldsAsync][yieldsAsync] ## Plugging in the fake Unlike [`sinon.spy`][spies] and [`sinon.stub`][stubs] methods, the `sinon.fake` API knows only how to *create* fakes, and doesn't concern itself with plugging them into the system under test. To plug the fakes into the system under test, you can use the [`sinon.replace*`][replace] methods. [rejects]: ./rejects [resolves]: ./resolves [returns]: ./returns [throws]: ./throws [yields]: ./yields [yieldsAsync]: ./yields-async [spies]: /concepts/spies/ [stubs]: /concepts/stubs/ [replace]: /concepts/sandboxes/api/replace [readFile]: https://nodejs.org/api/fs.html#fs_fs_readfile_path_options_callback [SUT]: https://en.wikipedia.org/wiki/System_under_test --- --- url: /getting-started.md description: >- Learn how to install and set up Sinon.JS with your test runner. Includes examples using Node-Tap, Mocha, Jest, and more. --- # Getting started The examples will be using [Node-Tap](https://node-tap.org), which is the simplest *complete* test runner that is known to Sinon maintainers. You can use Sinon with most popular test runners, such as [Mocha](https://mochajs.org), [Jasmine](https://jasmine.github.io), [Jest](https://jestjs.io), etc. ## Installing ```sh npm install -D sinon ``` ## Using ```js // import the test framework import tap from "tap"; // import sinon import * as sinon from "sinon"; tap.test("this is a test", (t) => { // create a fake const fake = sinon.fake(); // call the fake fake(); // assert on the fake t.ok(fake.calledOnce); t.end(); }); ``` ## Cleaning up after tests In order to avoid memory leaks, which could lead to unpredictable test failures, it is recommended to [`restore`][sandbox-restore] Sinon's default sandbox after each test. ```js t.afterEach((t) => { sinon.restore(); }); ``` For more advanced setups with sandboxes, please see the [sandbox](/concepts/sandboxes/) section. [default-sandbox]: ./sandboxes#default-sandbox [sandbox-restore]: /concepts/sandboxes/api/restore [fakes]: ./fakes [spies]: ./spies [stubs]: ./stubs --- --- url: /concepts/mocks/api.md description: Create a mock for the provided object. --- # Mock API ## Creating a mock Create a mock for the provided object. This does not change the object, but returns a mock object to set expectations on the object's methods. ## Methods * [expects][expects] * [restore][restore] * [verify][verify] ## Properties * [expectations][expectations] [expects]: ./expects [expectations]: ./expectations.md [restore]: ./restore [verify]: ./verify --- --- url: /concepts/sandboxes/api.md description: >- Since `sinon@5.0.0`, the `sinon` object is a default sandbox. Unless you have a very advanced setup or need a special configuration, you probably want to only use that one. --- # Sandbox API ## Default sandbox Since `sinon@5.0.0`, the `sinon` object is a default sandbox. Unless you have a very advanced setup or need a special configuration, you probably want to only use that one. ## Methods * [`createStubInstance`][create-stub-instance] * [`mock`][mock] * [`replace`][replace] * [`replaceGetter`][replace-getter] * [`replaceSetter`][replace-setter] * [`reset`][reset] * [`resetBehavior`][reset-behavior] * [`resetHistory`][reset-history] * [`restore`][restore] * [`spy`][spy] * [`stub`][stub] * [`useFakeTimers`][use-fake-timers] * [`verify`][verify] * [`verifyAndRestore`][verify-and-restore] ## Properties * [`assert`][assert] * [`leakThreshold`][leak-threshold] [assert]: ./assert [create-stub-instance]: ./create-stub-instance [leak-threshold]: ./leak-threshold [mock]: ./mock [replace]: ./replace [replace-getter]: ./replace-getter [replace-setter]: ./replace-setter [reset]: ./reset [reset-behavior]: ./reset-behavior [reset-history]: ./reset-history [restore]: ./restore [spy]: ./spy [stub]: ./stub [use-fake-timers]: ./use-fake-timers [verify]: ./verify [verify-and-restore]: ./verify-and-restore --- --- url: /concepts/spies/api.md description: >- Spy objects are objects returned from `sinon.spy()`. When spying on existing methods with `sinon.spy(object, method)`, the following properties and methods --- # Spy API ## Properties and methods on spied functions Spy objects are objects returned from `sinon.spy()`. When spying on existing methods with `sinon.spy(object, method)`, the following properties and methods are also available on `object.method`. ### Example ## Methods * [`alwaysCalledOn`](./always-called-on) * [`alwaysCalledWith`](./always-called-with) * [`alwaysCalledWithExactly`](./always-called-with-exactly) * [`alwaysCalledWithMatch`](./always-called-with-match) * [`alwaysReturned`](./always-returned) * [`alwaysThrew`](./always-threw) * [`calledAfter`](./called-after) * [`calledBefore`](./called-before) * [`calledImmediatelyAfter`](./called-immediately-after) * [`calledImmediatelyBefore`](./called-immediately-before) * [`calledOn`](./called-on) * [`calledOnceWithExactly`](./called-once-with-exactly) * [`calledWith`](./called-with) * [`calledWithExactly`](./called-with-exactly) * [`calledWithMatch`](./called-with-match) * [`calledWithNew`](./called-with-new) * [`calledOnceWith`](./called-once-with) * [`getCall`](./get-call) * [`getCalls`](./get-calls) * [`neverCalledWith`](./never-called-with) * [`neverCalledWithMatch`](./never-called-with-match) * [`printf`](./printf) * [`resetHistory`](./reset-history) * [`restore`](./restore) * [`returned`](./returned) * [`threw`](./threw) * [`withArgs`](./with-args) ## Properties * [`args`](./args) * [`callCount`](./call-count) * [`called`](./called) * [`calledOnce`](./called-once) * [`calledThrice`](./called-thrice) * [`calledTwice`](./called-twice) * [`exceptions`](./exceptions) * [`firstCall`](./first-call) * [`lastCall`](./last-call) * [`notCalled`](./not-called) * [`returnValues`](./return-values) * [`secondCall`](./second-call) * [`thirdCall`](./third-call) * [`thisValues`](./this-values) [call]: /concepts/spy-call/ [matchers]: /concepts/matchers/ --- --- url: /concepts/stubs/api.md description: Creates an anonymous stub function --- # Stub API ## Creating a stub ### `sinon.stub()` Creates an anonymous stub function ### `sinon.stub(object, "method")` Replaces `object.method` with a stub function. An exception is thrown if the property is not already a function. The original function can be restored by calling `object.method.restore()` (or `stub.restore()`). ## Advanced use ### `sinon.stub(obj)` Stubs all the object's methods. Note that it's usually better practice to stub individual methods, particularly on objects that you don't understand or control all the methods for (e.g. library dependencies). Stubbing individual methods shows intent more precisely and is less susceptible to unexpected behavior as the object's code evolves. ### `sinon.createStubInstance(MyConstructor, overrides)` When you want to create a stub object of `MyConstructor`, but don't want the constructor to be invoked, use this utility function. `overrides` is an optional map overriding created stubs, for example: is the same as: If provided value is not a stub, it will be used as the returned value: is the same as: ## Methods * [`addBehavior`][add-behavior] * [`callArg`][call-arg] * [`callArgWith`][call-arg-with] * [`callsArg`][calls-arg] * [`callsArgAsync`][calls-arg-async] * [`callsArgOn`][calls-arg-on] * [`callsArgOnWith`][calls-arg-on-with] * [`callsArgOnWithAsync`][calls-arg-on-with-async] * [`callsArgWith`][calls-arg-with] * [`callsArgWithAsync`][calls-arg-with-async] * [`callsFake`][calls-fake] * [`callsThrough`][calls-through] * [`callThroughWithNew`][call-through-with-new] * [`get`][get] * [`onCall`][on-call] * [`onFirstCall`][on-first-call] * [`onSecondCall`][on-second-call] * [`onThirdCall`][on-third-call] * [`rejects`][rejects] * [`reset`][reset] * [`resetBehavior`][resetBehavior] * [`resetHistory`][resetHistory] * [`resolves`][resolves] * [`resolvesArg`][resolvesArg] * [`returns`][returns] * [`returnsThis`][returnsThis] * [`set`][set] * [`throws`][throws] * [`throwsArg`][throws-arg] * [`value`][value] * [`withArgs`][with-args] * [`yield`][yield] * [`yieldTo`][yield-to] * [`yields`][yields] * [`yieldsAsync`][yields-async] * [`yieldsOn`][yields-on] * [`yieldsOnAsync`][yields-on-async] * [`yieldsRight`][yields-right] * [`yieldsTo`][yields-to] * [`yieldsToAsync`][yields-to-async] * [`yieldsToOn`][yields-to-on] * [`yieldsToOnAsync`][yields-to-on-async] ## Properties * [`wrappedMethod`][wrapped-method] *** [add-behavior]: ./add-behavior [call-arg]: ./call-arg [call-arg-with]: ./call-arg-with [calls-arg]: ./calls-arg [calls-arg-async]: ./calls-arg-async [calls-arg-on]: ./calls-arg-on [calls-arg-on-with]: ./calls-arg-on-with [calls-arg-on-with-async]: ./calls-arg-on-with-async [calls-arg-with]: ./calls-arg-with [calls-arg-with-async]: ./calls-arg-with-async [calls-fake]: ./calls-fake [calls-through]: ./calls-through [call-through-with-new]: ./call-through-with-new [get]: ./get [matchers]: /concepts/matchers/ [on-call]: ./on-call [on-first-call]: ./on-first-call [on-second-call]: ./on-second-call [on-third-call]: ./on-third-call [rejects]: ./rejects [reset]: ./reset [resetBehavior]: ./reset-behavior [resetHistory]: ./reset-history [resolves]: ./resolves [resolvesArg]: ./resolves-arg [returns]: ./returns [returnsThis]: ./returns-this [throws]: ./throws [set]: ./set [throws-arg]: ./throws-arg [value]: ./value [with-args]: ./with-args [wrapped-method]: ./wrapped-method [yield]: ./yield [yield-to]: ./yield-to [yields]: ./yields [yields-async]: ./yields-async [yields-on]: ./yields-on [yields-on-async]: ./yields-on-async [yields-right]: ./yields-right [yields-to]: ./yields-to [yields-to-async]: ./yields-to-async [yields-to-on]: ./yields-to-on [yields-to-on-async]: ./yields-to-on-async --- --- url: /concepts/matchers/api.md description: >- Flexible argument matching for assertions. Includes matchers for types, values, arrays, objects, and custom logic. --- * [`any`][any] * [`array`][array] * [`bool`][bool] * \[`date`]\[date] * [`defined`][defined] * [`every`][every] * [`falsy`][bool] * [`hasNested`][has-nested] * [`hasOwn`][has-own] * [`has`][has] * [`in`][in] * [`instanceOf`][instance-of] * [`map`][map] * [`match`][match] * [`number`][number] * [`object`][object] * [`regexp`][regexp] * [`same`][same] * [`set`][set] * [`some`][some] * [`string`][string] * [`symbol`][symbol] * [`truthy`][truthy] * [`typeOf`][type-of] [any]: ./any [array]: ./array [bool]: ./bool [defined]: ./defined [every]: ./every [falsy]: ./falsy [has-nested]: ./has-nested [has-own]: ./has-own [has]: ./has [in]: ./in [instance-of]: ./instance-of [map]: ./map [match]: ./match [number]: ./number [object]: ./object [regexp]: ./regexp [same]: ./same [set]: ./set [some]: ./some [string]: ./string [symbol]: ./symbol [truthy]: ./truthy [type-of]: ./type-of --- --- url: /concepts/mocks/migrating-from-mocks.md description: >- Guide for migrating from mocks to fakes with explicit assertions. Learn why separating concerns leads to better tests. --- # Migrating from Mocks to Fakes Mocks combine three concerns: behavior replacement, call observation, and assertions. Modern testing prefers separation of concerns using [fakes](/concepts/fakes/) with explicit assertions. This guide shows how to migrate from mocks to simpler alternatives. ## Why Migrate? **Mocks are powerful but have drawbacks:** 1. **Brittle tests** - Tests break when implementation changes 2. **Coupled to internals** - Tests know too much about how code works 3. **Complex failures** - Mock errors can be cryptic 4. **Upfront expectations** - Requires predicting all interactions **Fakes with explicit assertions are better:** 1. **Flexible tests** - Tests focus on behavior, not implementation 2. **Clear intent** - Assertions show what matters 3. **Better errors** - Assertion failures are direct 4. **After-the-fact verification** - Test what actually happened ## When to Keep Using Mocks Keep mocks when: 1. **Upfront expectations clarify intent** - The expectation itself documents behavior 2. **Immediate feedback wanted** - Fail fast on first unexpected call 3. **Complex interaction patterns** - Multiple related expectations on one object For most cases, use fakes. ## Migration Patterns ### Basic Expectation → Fake + Assertion **Before (Mock):** ```javascript const obj = { method() { return "original"; } }; const mock = sinon.mock(obj); mock.expects("method").once(); obj.method(); mock.verify(); ``` **After (Fake):** ```javascript const obj = { method() { return "original"; } }; const fake = sinon.fake.returns("result"); sinon.replace(obj, "method", fake); obj.method(); assert.ok(fake.calledOnce); ``` **Benefits:** * Clearer: Shows what we're checking (calledOnce) * More flexible: Can add more assertions * Better errors: "expected true to be true" vs mock expectation message *** ### Argument Expectations → Fake + Argument Assertion **Before (Mock):** ```javascript mock.expects("greet").withArgs("Alice"); obj.greet("Alice"); mock.verify(); ``` **After (Fake):** ```javascript const fake = sinon.fake(); sinon.replace(obj, "greet", fake); obj.greet("Alice"); assert.ok(fake.calledWith("Alice")); ``` **Benefits:** * Can check multiple argument patterns * Can assert on partial matches * Better error messages *** ### Multiple Calls → Fake + Call Count **Before (Mock):** ```javascript mock.expects("log").atLeast(2).atMost(5); // ... actions ... mock.verify(); ``` **After (Fake):** ```javascript const fake = sinon.fake(); sinon.replace(logger, "log", fake); // ... actions ... assert.ok(fake.callCount >= 2, "called at least twice"); assert.ok(fake.callCount <= 5, "called at most 5 times"); ``` **Benefits:** * More flexible assertion logic * Can check exact count or ranges * Can inspect actual call count *** ### Return Value Control → Fake Factory **Before (Mock - using stub behavior):** ```javascript mock.expects("getData").once().returns({ id: 1, name: "Alice" }); const result = obj.getData(); mock.verify(); ``` **After (Fake):** ```javascript const fake = sinon.fake.returns({ id: 1, name: "Alice" }); sinon.replace(obj, "getData", fake); const result = obj.getData(); assert.deepEqual(result, { id: 1, name: "Alice" }); assert.ok(fake.calledOnce); ``` **Benefits:** * Behavior and verification separated * Can verify behavior independently * Clearer test structure *** ### Sequential Behavior → Multiple Fakes or Stub **Before (Mock with stub behavior):** ```javascript mock .expects("fetch") .onFirstCall() .returns("first") .onSecondCall() .returns("second"); obj.fetch(); // 'first' obj.fetch(); // 'second' mock.verify(); ``` **After Option 1 (Keep using stub for this case):** ```javascript const stub = sinon .stub(obj, "fetch") .onFirstCall() .returns("first") .onSecondCall() .returns("second"); obj.fetch(); // 'first' obj.fetch(); // 'second' assert.ok(stub.calledTwice); ``` **After Option 2 (Fake with custom logic):** ```javascript const calls = ["first", "second"]; let callIndex = 0; const fake = sinon.fake(() => calls[callIndex++]); sinon.replace(obj, "fetch", fake); obj.fetch(); // 'first' obj.fetch(); // 'second' assert.equal(fake.callCount, 2); ``` **Note:** For sequential behavior, stubs are often the best choice. *** ### Context Expectations → Fake + This Check **Before (Mock):** ```javascript const obj1 = { name: "obj1" }; const obj2 = { name: "obj2", method() {} }; const mock = sinon.mock(obj2); mock.expects("method").on(obj1); obj2.method.call(obj1); mock.verify(); ``` **After (Fake):** ```javascript const obj1 = { name: "obj1" }; const obj2 = { name: "obj2", method() {} }; const fake = sinon.fake(); sinon.replace(obj2, "method", fake); obj2.method.call(obj1); assert.equal(fake.thisValues[0], obj1); ``` **Benefits:** * Access to all `this` values * Can check multiple calls * More flexible assertions *** ### Never Called → Fake + Not Called **Before (Mock):** ```javascript mock.expects("dangerousMethod").never(); // ... actions ... mock.verify(); ``` **After (Fake):** ```javascript const fake = sinon.fake(); sinon.replace(obj, "dangerousMethod", fake); // ... actions ... assert.ok(fake.notCalled, "dangerous method should not be called"); ``` **Benefits:** * Clearer assertion * Better error message * Can be part of larger test *** ### Multiple Expectations → Multiple Assertions **Before (Mock):** ```javascript const mock = sinon.mock(service); mock.expects("load").once(); mock.expects("process").once(); mock.expects("save").once(); // ... actions ... mock.verify(); ``` **After (Fake):** ```javascript const loadFake = sinon.fake.returns("data"); const processFake = sinon.fake.returns("processed"); const saveFake = sinon.fake.resolves(); sinon.replace(service, "load", loadFake); sinon.replace(service, "process", processFake); sinon.replace(service, "save", saveFake); // ... actions ... assert.ok(loadFake.calledOnce, "load called once"); assert.ok(processFake.calledOnce, "process called once"); assert.ok(saveFake.calledOnce, "save called once"); ``` **Benefits:** * Each assertion is independent * Failures are more specific * Can verify order if needed: `sinon.assert.callOrder(loadFake, processFake, saveFake)` *** ## Anti-Pattern: Testing Implementation Mocks make it easy to test implementation details. Avoid this: **Bad (Mock on implementation):** ```javascript // Testing HOW the code works const mock = sinon.mock(database); mock.expects("query").withArgs("SELECT * FROM users"); mock.expects("query").withArgs("SELECT * FROM posts"); controller.getUserWithPosts(userId); mock.verify(); ``` **Good (Fake on behavior):** ```javascript // Testing WHAT the code does const fake = sinon.fake.resolves({ user: userData, posts: postsData }); sinon.replace(database, "getUserWithPosts", fake); const result = await controller.getUserWithPosts(userId); assert.deepEqual(result.user, expectedUser); assert.deepEqual(result.posts, expectedPosts); assert.ok(fake.calledWith(userId)); ``` **Why this is better:** * Test survives database query changes * Focuses on behavior, not implementation * Easier to understand what's being tested *** ## Refactoring Strategy ### Step 1: Identify Mock Usage Find all `sinon.mock()` usage in your codebase: ```bash grep -r "sinon.mock" test/ ``` ### Step 2: Categorize by Complexity **Simple mocks** (one expectation, no fancy behavior): * Migrate immediately to fakes **Medium mocks** (multiple expectations, simple behavior): * Consider if all expectations are necessary * Migrate useful ones to fakes + assertions **Complex mocks** (many expectations, complex behavior): * Keep as mocks if they clarify intent * Or break into smaller, focused tests ### Step 3: Migrate One at a Time For each mock: 1. **Understand the expectation** - What's being verified? 2. **Replace with fake** - Use appropriate fake factory 3. **Add assertions** - Explicitly check what matters 4. **Run tests** - Ensure behavior unchanged 5. **Commit** - Small, focused commits ### Step 4: Simplify After migration: * Remove unnecessary verifications * Combine related assertions * Focus tests on behavior ## Decision Tree ``` Is the expectation about behavior or implementation? ├─ Behavior → Use fake + explicit assertion └─ Implementation → Do you really need to test this? ├─ Yes, it's critical → Keep mock (rare) └─ No, it's an implementation detail → Refactor test to check behavior ``` ## Migration Checklist * \[ ] Find all `sinon.mock()` usage * \[ ] For each mock: * \[ ] Identify what's being verified * \[ ] Determine if verification is necessary * \[ ] Replace mock with fake * \[ ] Add explicit assertions * \[ ] Remove unnecessary expectations * \[ ] Update test to focus on behavior * \[ ] Run all tests * \[ ] Review test clarity and maintainability ## Benefits After Migration 1. **More maintainable** - Tests survive refactoring 2. **Clearer intent** - Assertions show what matters 3. **Better errors** - Know exactly what failed 4. **More flexible** - Can verify in different ways 5. **Less brittle** - Implementation changes don't break tests ## See Also * [Fakes Documentation](/concepts/fakes/) * [Mock API Documentation](/concepts/mocks/) * [When NOT to use mocks](./#when-to-not-use-mocks) * [Error Handling](./error-handling) * [Martin Fowler: Mocks Aren't Stubs](https://martinfowler.com/articles/mocksArentStubs.html) --- --- url: /concepts/spies/migrating-to-fakes.md description: >- Guide for migrating from spies to fakes. Learn why fakes are preferred and see code examples for common migration patterns. --- # Migrating from Spies to Fakes [Fakes](/concepts/fakes/) are the modern, preferred alternative to spies. They provide the same functionality with a simpler, more consistent API. This guide shows how to migrate your code from spies to fakes. ## Why Migrate to Fakes? 1. **Immutable** - Fakes are immutable, avoiding confusion from mutable behavior 2. **Simpler API** - No need to understand stub vs spy differences 3. **Same spy API** - All spy properties and methods work on fakes 4. **Easier to use** - More intuitive patterns for common use cases ## Migration Patterns ### Anonymous Function Spies **Before (Spy):** ```javascript const spy = sinon.spy(); callback(spy); assert(spy.calledOnce); ``` **After (Fake):** ```javascript const fake = sinon.fake(); callback(fake); assert(fake.calledOnce); ``` **Change:** Replace `sinon.spy()` with `sinon.fake()` *** ### Wrapping Functions **Before (Spy):** ```javascript function myFunc(x) { return x * 2; } const spy = sinon.spy(myFunc); const result = spy(5); assert.equal(result, 10); assert(spy.calledWith(5)); ``` **After (Fake):** ```javascript function myFunc(x) { return x * 2; } const fake = sinon.fake(myFunc); const result = fake(5); assert.equal(result, 10); assert(fake.calledWith(5)); ``` **Change:** Replace `sinon.spy(func)` with `sinon.fake(func)` *** ### Wrapping Object Methods **Before (Spy):** ```javascript const obj = { method() { return "original"; } }; const spy = sinon.spy(obj, "method"); obj.method(); assert(spy.calledOnce); spy.restore(); ``` **After (Fake):** ```javascript const obj = { method() { return "original"; } }; const fake = sinon.fake(obj.method); sinon.replace(obj, "method", fake); obj.method(); assert(fake.calledOnce); sinon.restore(); ``` **Change:** Use `sinon.replace()` to plug the fake into the object. This keeps creation separate from plugging in, following the single responsibility principle. *** ### Return Value Stubbing **Before (Spy/Stub):** ```javascript const stub = sinon.stub().returns(42); const result = stub(); assert.equal(result, 42); ``` **After (Fake):** ```javascript const fake = sinon.fake.returns(42); const result = fake(); assert.equal(result, 42); ``` **Change:** Use `sinon.fake.returns(value)` for simple return values *** ### Throwing Errors **Before (Spy/Stub):** ```javascript const stub = sinon.stub().throws(new Error("Failed")); assert.throws(() => stub()); ``` **After (Fake):** ```javascript const fake = sinon.fake.throws(new Error("Failed")); assert.throws(() => fake()); ``` **Change:** Use `sinon.fake.throws(error)` for throwing errors *** ### Promise Resolution **Before (Spy/Stub):** ```javascript const stub = sinon.stub().resolves("success"); await stub(); ``` **After (Fake):** ```javascript const fake = sinon.fake.resolves("success"); await fake(); ``` **Change:** Use `sinon.fake.resolves(value)` *** ### Promise Rejection **Before (Spy/Stub):** ```javascript const stub = sinon.stub().rejects(new Error("Failed")); try { await stub(); } catch (e) { assert.equal(e.message, "Failed"); } ``` **After (Fake):** ```javascript const fake = sinon.fake.rejects(new Error("Failed")); try { await fake(); } catch (e) { assert.equal(e.message, "Failed"); } ``` **Change:** Use `sinon.fake.rejects(error)` *** ### Callback Invocation (Sync) **Before (Spy/Stub):** ```javascript const stub = sinon.stub().yields("arg1", "arg2"); stub((a, b) => { assert.equal(a, "arg1"); assert.equal(b, "arg2"); }); ``` **After (Fake):** ```javascript const fake = sinon.fake.yields("arg1", "arg2"); fake((a, b) => { assert.equal(a, "arg1"); assert.equal(b, "arg2"); }); ``` **Change:** Use `sinon.fake.yields(args...)` *** ### Callback Invocation (Async) **Before (Spy/Stub):** ```javascript const stub = sinon.stub().yieldsAsync("arg1", "arg2"); stub((a, b) => { assert.equal(a, "arg1"); assert.equal(b, "arg2"); }); ``` **After (Fake):** ```javascript const fake = sinon.fake.yieldsAsync("arg1", "arg2"); fake((a, b) => { assert.equal(a, "arg1"); assert.equal(b, "arg2"); }); ``` **Change:** Use `sinon.fake.yieldsAsync(args...)` ## Key Differences ### Immutability **Spies/Stubs (Mutable):** ```javascript const stub = sinon.stub(); stub.returns(1); // Changes behavior stub.returns(2); // Changes behavior again ``` **Fakes (Immutable):** ```javascript const fake1 = sinon.fake.returns(1); // Fixed behavior const fake2 = sinon.fake.returns(2); // New fake, different behavior // fake1 still returns 1, fake2 returns 2 ``` ### Creation vs Plugging In **Spies (Combined):** ```javascript // Spy creation and plugging in are combined sinon.spy(obj, "method"); ``` **Fakes (Separated):** ```javascript // Creation and plugging in are separate const fake = sinon.fake(); sinon.replace(obj, "method", fake); ``` This separation makes the responsibilities clearer and more testable. ## Migration Checklist * \[ ] Replace `sinon.spy()` with `sinon.fake()` * \[ ] Replace `sinon.spy(func)` with `sinon.fake(func)` * \[ ] Replace `sinon.spy(obj, "method")` with `sinon.replace(obj, "method", sinon.fake(obj.method))` * \[ ] Replace stub creation with fake factories (`fake.returns()`, `fake.throws()`, etc.) * \[ ] Update test assertions (same spy API works on fakes) * \[ ] Verify all tests still pass ## When to Keep Spies You might want to keep using spies if: 1. **Legacy codebase** - Large existing test suite using spies 2. **Team familiarity** - Team is more comfortable with spy API 3. **Property accessors** - Using `sinon.spy(obj, "prop", ["get", "set"])` (no direct fake equivalent) For new code, prefer fakes. ## See Also * [Fakes Documentation](/concepts/fakes/) * [Spy API Documentation](./api/) * [Why Fakes Over Spies](/concepts/fakes/#prefer-fakes-over-spies-and-stubs) --- --- url: /concepts/stubs/migrating-to-fakes.md description: >- Guide for migrating from stubs to fakes. Learn why fakes are preferred and see code examples for common migration patterns. --- # Migrating from Stubs to Fakes [Fakes][fakes] are the modern, preferred alternative to stubs for most use cases. They provide simpler, immutable behavior while maintaining the same spy API. This guide shows how to migrate your code from stubs to fakes. ## Why Migrate to Fakes? 1. **Immutable** - Fakes can't change behavior after creation, preventing bugs 2. **Simpler** - No need to chain multiple behavior methods 3. **Same spy API** - All spy assertions work on fakes 4. **Clearer intent** - Behavior is defined at creation time ## When to Keep Using Stubs Stubs have unique features that fakes don't provide: 1. **Call-specific behavior** - `onCall()`, `onFirstCall()`, `onSecondCall()`, `onThirdCall()` 2. **Argument-based behavior** - `withArgs()` for different returns per arguments 3. **Property stubbing** - `.get()`, `.set()`, `.value()` for property accessors 4. **Mutable behavior** - When you need to change behavior mid-test (usually a code smell) For these cases, continue using stubs. For everything else, prefer fakes. ## Migration Patterns ### Simple Return Values **Before (Stub):** ```javascript const stub = sinon.stub().returns(42); const result = stub(); assert.equal(result, 42); ``` **After (Fake):** ```javascript const fake = sinon.fake.returns(42); const result = fake(); assert.equal(result, 42); ``` **Change:** Replace `sinon.stub().returns()` with `sinon.fake.returns()` *** ### Returning Arguments **Before (Stub):** ```javascript const stub = sinon.stub().returnsArg(0); stub("hello"); // returns 'hello' stub("world"); // returns 'world' ``` **After (Fake):** ```javascript // Fakes don't have returnsArg, use callsFake const fake = sinon.fake((arg) => arg); fake("hello"); // returns 'hello' fake("world"); // returns 'world' ``` **Change:** Use `sinon.fake(func)` with a function that returns the argument *** ### Throwing Errors **Before (Stub):** ```javascript const stub = sinon.stub().throws(new Error("Failed")); assert.throws(() => stub()); ``` **After (Fake):** ```javascript const fake = sinon.fake.throws(new Error("Failed")); assert.throws(() => fake()); ``` **Change:** Replace `sinon.stub().throws()` with `sinon.fake.throws()` *** ### Promise Resolution **Before (Stub):** ```javascript const stub = sinon.stub().resolves("success"); const result = await stub(); assert.equal(result, "success"); ``` **After (Fake):** ```javascript const fake = sinon.fake.resolves("success"); const result = await fake(); assert.equal(result, "success"); ``` **Change:** Replace `sinon.stub().resolves()` with `sinon.fake.resolves()` *** ### Promise Rejection **Before (Stub):** ```javascript const stub = sinon.stub().rejects(new Error("Failed")); try { await stub(); } catch (e) { assert.equal(e.message, "Failed"); } ``` **After (Fake):** ```javascript const fake = sinon.fake.rejects(new Error("Failed")); try { await fake(); } catch (e) { assert.equal(e.message, "Failed"); } ``` **Change:** Replace `sinon.stub().rejects()` with `sinon.fake.rejects()` *** ### Custom Function Logic **Before (Stub):** ```javascript const stub = sinon.stub().callsFake(function (x) { return x * 2; }); ``` **After (Fake):** ```javascript const fake = sinon.fake(function (x) { return x * 2; }); ``` **Change:** Pass function directly to `sinon.fake()` instead of chaining `.callsFake()` *** ### Callback Invocation (Sync) **Before (Stub):** ```javascript const stub = sinon.stub().yields("error", "data"); stub((err, data) => { assert.equal(err, "error"); assert.equal(data, "data"); }); ``` **After (Fake):** ```javascript const fake = sinon.fake.yields("error", "data"); fake((err, data) => { assert.equal(err, "error"); assert.equal(data, "data"); }); ``` **Change:** Replace `sinon.stub().yields()` with `sinon.fake.yields()` *** ### Callback Invocation (Async) **Before (Stub):** ```javascript const stub = sinon.stub().yieldsAsync("error", "data"); // callback invoked asynchronously ``` **After (Fake):** ```javascript const fake = sinon.fake.yieldsAsync("error", "data"); // callback invoked asynchronously ``` **Change:** Replace `sinon.stub().yieldsAsync()` with `sinon.fake.yieldsAsync()` *** ### Replacing Object Methods **Before (Stub):** ```javascript const obj = { method() { return "original"; } }; sinon.stub(obj, "method").returns("stubbed"); obj.method(); // 'stubbed' ``` **After (Fake):** ```javascript const obj = { method() { return "original"; } }; const fake = sinon.fake.returns("stubbed"); sinon.replace(obj, "method", fake); obj.method(); // 'stubbed' ``` **Change:** Create fake separately, then use `sinon.replace()` to plug it in *** ## Patterns That Require Stubs ### Call-Specific Behavior (Keep Stubs) Some scenarios require call-specific behavior which fakes don't support: ```javascript // This pattern requires stubs const stub = sinon .stub() .onFirstCall() .returns(1) .onSecondCall() .returns(2) .returns(3); stub(); // 1 stub(); // 2 stub(); // 3 stub(); // 3 ``` **No fake equivalent** - Continue using stubs for this pattern. **Alternative:** If you control the calls, create separate fakes: ```javascript const fake1 = sinon.fake.returns(1); const fake2 = sinon.fake.returns(2); const fake3 = sinon.fake.returns(3); // Use appropriate fake at each call site ``` *** ### Argument-Based Behavior (Keep Stubs) Returning different values based on arguments requires stubs: ```javascript // This pattern requires stubs const stub = sinon.stub(); stub.withArgs("apple").returns("fruit"); stub.withArgs("carrot").returns("vegetable"); stub.returns("unknown"); stub("apple"); // 'fruit' stub("carrot"); // 'vegetable' stub("pizza"); // 'unknown' ``` **No fake equivalent** - Continue using stubs for this pattern. **Alternative:** Use a fake with conditional logic: ```javascript const fake = sinon.fake((food) => { if (food === "apple") return "fruit"; if (food === "carrot") return "vegetable"; return "unknown"; }); ``` *** ### Property Stubbing (Keep Stubs) Stubbing property getters/setters requires stubs: ```javascript // This pattern requires stubs const obj = { get name() { return "Alice"; } }; sinon.stub(obj, "name").get(() => "Bob"); obj.name; // 'Bob' ``` **No fake equivalent** - Continue using stubs for property stubbing. *** ## Key Differences ### Immutability **Stubs (Mutable):** ```javascript const stub = sinon.stub(); stub.returns(1); // Changes behavior stub(); // 1 stub.returns(2); // Changes behavior again stub(); // 2 ``` **Fakes (Immutable):** ```javascript const fake1 = sinon.fake.returns(1); const fake2 = sinon.fake.returns(2); fake1(); // 1 fake2(); // 2 // fake1 still returns 1, fake2 returns 2 ``` ### Behavior Chaining **Stubs (Chained):** ```javascript const stub = sinon.stub().onFirstCall().returns(1).onSecondCall().returns(2); ``` **Fakes (No Chaining):** ```javascript // Behavior defined at creation const fake = sinon.fake.returns(1); // No way to change behavior ``` ### Creation vs Plugging In **Stubs (Combined):** ```javascript // Stub creation and plugging in are combined sinon.stub(obj, "method").returns(42); ``` **Fakes (Separated):** ```javascript // Creation and plugging in are separate const fake = sinon.fake.returns(42); sinon.replace(obj, "method", fake); ``` ## Migration Checklist * \[ ] Identify all `sinon.stub()` usage * \[ ] Check for call-specific behavior (`onCall`, `onFirstCall`, etc.) - keep as stub * \[ ] Check for argument-based behavior (`withArgs`) - keep as stub * \[ ] Check for property stubbing (`.get()`, `.set()`, `.value()`) - keep as stub * \[ ] Replace simple stubs with appropriate fake factories: * \[ ] `stub().returns()` → `fake.returns()` * \[ ] `stub().throws()` → `fake.throws()` * \[ ] `stub().resolves()` → `fake.resolves()` * \[ ] `stub().rejects()` → `fake.rejects()` * \[ ] `stub().yields()` → `fake.yields()` * \[ ] `stub().yieldsAsync()` → `fake.yieldsAsync()` * \[ ] `stub().callsFake()` → `fake(func)` * \[ ] Update object method stubbing to use `sinon.replace()` * \[ ] Verify all tests still pass ## Decision Tree ``` Do you need call-specific behavior (onCall, onFirstCall)? ├─ Yes → Keep using stub └─ No → Do you need argument-based behavior (withArgs)? ├─ Yes → Keep using stub └─ No → Do you need property stubbing (.get, .set, .value)? ├─ Yes → Keep using stub └─ No → Migrate to fake! ✨ ``` ## See Also * [Fakes Documentation][fakes] * [Stub API Documentation](./api/) * [Why Fakes Over Stubs](/concepts/fakes/#prefer-fakes-over-spies-and-stubs) * [Spy to Fake Migration](/concepts/spies/migrating-to-fakes) [fakes]: /concepts/fakes/ --- --- url: /concepts/mocks/api/_index.md --- # Mock API ## Creating a mock Create a mock for the provided object. This does not change the object, but returns a mock object to set expectations on the object's methods. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("mock - creating a mock", (t) => { const obj = { greet: function (name) { return `Hello ${name}`; } }; const mock = sinon.mock(obj); t.ok(mock, "mock object created"); t.type(mock.expects, "function", "mock has expects method"); t.type(mock.verify, "function", "mock has verify method"); t.type(mock.restore, "function", "mock has restore method"); mock.restore(); t.end(); }); ``` ## Methods * [expects][expects] * [restore][restore] * [verify][verify] ## Properties * [expectations][expectations] [expects]: ./expects [expectations]: ./expectations.md [restore]: ./restore [verify]: ./verify --- --- url: /concepts/sandboxes/api/_index.md --- # Sandbox API ## Default sandbox Since `sinon@5.0.0`, the `sinon` object is a default sandbox. Unless you have a very advanced setup or need a special configuration, you probably want to only use that one. ```js import t from "tap"; import sinon from "sinon"; t.test("default sandbox can stub and restore properties", (t) => { const myObject = { hello: "world" }; // Stub the property sinon.stub(myObject, "hello").value("Sinon"); // Verify the stub works t.equal(myObject.hello, "Sinon", "property should be stubbed to 'Sinon'"); // Restore sinon.restore(); // Verify restoration t.equal(myObject.hello, "world", "property should be restored to 'world'"); t.end(); }); ``` ## Methods * [`createStubInstance`](./create-stub-instance) * [`mock`](./mock) * [`replace`](./replace) * [`replaceGetter`](./replace-getter) * [`replaceSetter`](./replace-setter) * [`reset`](./reset) * [`resetBehavior`](./reset-behavior) * [`resetHistory`](./reset-history) * [`restore`](./restore) * [`spy`](./spy) * [`stub`](./stub) * [`useFakeTimers`](./use-fake-timers) * [`verify`](./verify) * [`verifyAndRestore`](./verify-and-restore) ## Properties * [`assert`](./assert) * [`leakThreshold`](./leak-threshold) --- --- url: /concepts/sandboxes/create-sandbox.md description: >- Creates a sandbox for grouping fakes, spies, and stubs with automatic cleanup. Use only for advanced scenarios. --- **⚠️ WARNING ⚠️** **Unless you have very advanced scenarios, you will not need to use `createSandbox` and should use the [default sandbox](/concepts/sandboxes/#default-sandbox) on the `sinon` object itself.** *** # `var sandbox = sinon.createSandbox();` Creates a new sandbox object with spies, stubs, and mocks. # `sinon.createSandbox(config)` Creates a new sandbox object with it's own set of spies, stubs, and mocks. The `createSandbox` method is mostly an integration feature, and can be used for advanced scenarios including a global object to coordinate all fakes through. Sandboxes are partially configured by default such that calling: ```js import t from "tap"; import sinon from "sinon"; t.test("createSandbox with empty config uses defaults", (t) => { const sandbox = sinon.createSandbox({}); // Verify sandbox has expected methods t.ok(sandbox.spy, "sandbox should have spy method"); t.ok(sandbox.stub, "sandbox should have stub method"); t.ok(sandbox.mock, "sandbox should have mock method"); // Verify useFakeTimers is false by default t.notOk(sandbox.clock, "sandbox should not have clock by default"); sandbox.restore(); t.end(); }); ``` will merge in extra defaults analogous to: ```js import t from "tap"; import sinon from "sinon"; t.test("createSandbox merges in default configuration", (t) => { const sandbox = sinon.createSandbox({ injectInto: null, properties: ["spy", "stub", "mock"], useFakeTimers: false }); // Verify the configuration is applied t.ok(sandbox.spy, "sandbox should have spy"); t.ok(sandbox.stub, "sandbox should have stub"); t.ok(sandbox.mock, "sandbox should have mock"); t.notOk( sandbox.clock, "sandbox should not have clock when useFakeTimers is false" ); sandbox.restore(); t.end(); }); ``` `useFakeTimers` is **false** by default in `createSandbox`, unlike the default sandbox where fake timers are enabled: To get a full sandbox with stubs, spies, etc. **and** fake timers, you can call: ```js import t from "tap"; import sinon from "sinon"; t.test("createSandbox with useFakeTimers creates clock", (t) => { // Create sandbox with useFakeTimers explicitly const sandbox = sinon.createSandbox({ useFakeTimers: true }); t.ok(sandbox.clock, "sandbox with useFakeTimers:true should have clock"); t.ok(sandbox.clock.tick, "clock should have tick method"); sandbox.restore(); t.end(); }); ``` ### `injectInto` The sandbox's methods can be injected into another object for convenience. The `injectInto` configuration option can name an object to add properties to. ### `properties` The list of properties that can be injected are the ones exposed by the object returned by the function `inject`, namely: ```js import t from "tap"; import sinon from "sinon"; t.test("createSandbox available properties list", (t) => { const availableProperties = [ "spy", "stub", "mock", "createStubInstance", "fake", "replace", "replaceSetter", "replaceGetter", "clock", "match" ]; // Create sandbox with all properties const sandbox = sinon.createSandbox({ properties: availableProperties, useFakeTimers: true }); // Verify key properties exist t.ok(sandbox.spy, "sandbox should have spy"); t.ok(sandbox.stub, "sandbox should have stub"); t.ok(sandbox.mock, "sandbox should have mock"); t.ok(sandbox.fake, "sandbox should have fake"); t.ok(sandbox.clock, "sandbox should have clock"); sandbox.restore(); t.end(); }); ``` ### `useFakeTimers` If set to `true`, the sandbox will have a `clock` property. You can optionally pass in a configuration object that follows the [specification for fake timers](/concepts/fake-timers/), such as `{ toFake: ["setTimeout", "setInterval"] }`. ### exposing sandbox example To create an object `sandboxFacade` which gets the method `spy` injected, you can code: ```js import t from "tap"; import sinon from "sinon"; t.test("createSandbox with injectInto injects methods into object", (t) => { // Object that will have the spy method injected into it const sandboxFacade = {}; // Create sandbox and inject properties (in this case spy) into sandboxFacade const sandbox = sinon.createSandbox({ injectInto: sandboxFacade, properties: ["spy"] }); // Verify spy method was injected t.ok(sandboxFacade.spy, "sandboxFacade should have spy method"); t.type(sandboxFacade.spy, "function", "spy should be a function"); // Verify the injected method works const obj = { method: function () {} }; sandboxFacade.spy(obj, "method"); obj.method(); t.ok(obj.method.calledOnce, "injected spy should track calls"); sandbox.restore(); t.end(); }); ``` --- --- url: /concepts/matchers/api/date.md description: Requires the value to be a `Date` object. --- # `sinon.match.date` Requires the value to be a `Date` object. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sinon.match.date", (t) => { const fake = sinon.fake(); fake(new Date()); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.date); }, "should accept Date"); fake(new Date("invalid")); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.date); }, "should accept invalid Date"); fake.resetHistory(); fake(Date.now()); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.date), /expected fake to be called with match/, "should reject number" ); fake.resetHistory(); fake("2026-02-12"); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.date), /expected fake to be called with match/, "should reject string" ); t.end(); }); ``` --- --- url: /concepts/matchers/api/falsy.md description: Requires the value to be falsy. --- # `sinon.match.falsy` Requires the value to be falsy. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sinon.match.falsy", (t) => { const fake = sinon.fake(); fake(false); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.falsy); }, "should accept false"); fake(0); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.falsy); }, "should accept zero"); fake(""); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.falsy); }, "should accept empty string"); fake(null); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.falsy); }, "should accept null"); fake.resetHistory(); fake(true); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.falsy), /expected fake to be called with match/, "should reject true" ); fake.resetHistory(); fake("hello"); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.falsy), /expected fake to be called with match/, "should reject non-empty string" ); t.end(); }); ``` --- --- url: /concepts/matchers/api/func.md description: Requires the value to be a `Function`. --- # `sinon.match.func` Requires the value to be a `Function`. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("sinon.match.func", (t) => { const fake = sinon.fake(); fake(function () {}); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.func); }, "should accept function"); fake(() => {}); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.func); }, "should accept arrow function"); fake.resetHistory(); fake({ call: () => {} }); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.func), /expected fake to be called with match/, "should reject object" ); fake.resetHistory(); fake("function"); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.func), /expected fake to be called with match/, "should reject string" ); t.end(); }); ``` --- --- url: /sponsors.md description: >- Thank you to the individuals and organizations who sponsor Sinon.JS development. --- ## Proudly Sponsored By ## Proudly Backed By ## Become a sponsor Support Sinon.JS development through one of these platforms: * **[OpenCollective](https://opencollective.com/sinon/)** — Become a backer or sponsor with a monthly donation. Your logo will appear on our README and website. * **[GitHub Sponsors](https://github.com/sponsors/sinonjs)** — Sponsor our work directly through GitHub. * **[Thanks.dev](https://thanks.dev/d/gh/sinonjs)** — Fund Sinon.JS via dependency-based donations. Thanks.dev automatically distributes funds to the projects your code depends on. **Sponsorship tiers include:** * **Backer** — Individual supporters who want to help keep the project alive * **Sponsor** — Organizations who want their logo featured on our README and website * **Gold/Platinum Sponsor** — Higher tiers with increased visibility and recognition --- --- url: /concepts/spy-call/api.md description: >- Access call details like arguments, return value, this context, and exceptions for each spy invocation. --- ## Methods * [`calledAfter`](./called-after) * [`calledBefore`](./called-before) * [`calledImmediatelyAfter`](./called-immediately-after) * [`calledImmediatelyBefore`](./called-immediately-before) * [`calledOn`](./called-on) * [`calledWith`](./called-with) * [`calledWithExactly`](./called-with-exactly) * [`calledWithMatch`](./called-with-match) * [`notCalledWith`](./not-called-with) * [`notCalledWithMatch`](./not-called-with-match) * [`returned`](./returned) * [`threw`](./threw) ## Properties * [`args`](./args) * [`callback`](./callback) * [`exception`](./exception) * [`firstArg`](./first-arg) * [`lastArg`](./last-arg) * [`returnValue`](./return-value) * [`thisValue`](./this-value) --- --- url: /concepts/stubs/api/calls-arg-on-async.md description: >- Causes the stub to call the argument at the provided `index` as a callback function, with an additional `object` parameter to pass the [`this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) context, asynchronously. --- # `stub.callsArgOnAsync(index, object)` Causes the stub to call the argument at the provided `index` as a callback function, with an additional `object` parameter to pass the [`this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) context, asynchronously. ```js import * as sinon from "sinon"; const car = { color: "red" }; const stub = sinon.stub().callsArgOnAsync(0, car); function updateColor() { this.color = "blue"; } console.log(car.color); // => red stub(updateColor); console.log(car.color); // => red setTimeout(function () { console.log(car.color); // => blue }, 1); ``` ## Errors When the argument at the provided `index` is `undefined`, or not a function, an `Error` will be thrown. ```js import * as sinon from "sinon"; const car = { color: "red" }; const stub = sinon.stub().callsArgOnAsync(0, car); const pie = "apple pie"; stub(pie); // => Uncaught TypeError: argument at index 0 is not a function: apple pie stub(nonExistingCallback); // => Uncaught ReferenceError: nonExistingCallback is not defined ``` ## See also * [stub.callsArg](./calls-arg) * [stub.callsArgAsync](./calls-arg-async) * [stub.callsArgOn](./calls-arg-on) * [stub.callsArgOnWith](./calls-arg-on-with) * [stub.callsArgOnWithAsync](./calls-arg-on-with-async) * [stub.callsArgWith](./calls-arg-with) * [stub.callsArgWithAsync](./calls-arg-with-async) ## More information * https://nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick * https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop * https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout * YouTube: ["JavaScript Visualized - Event Loop, Web APIs, (Micro)task Queue"](https://www.youtube.com/watch?v=eiC58R16hb8) by Lydia Hallie * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this --- --- url: /concepts/stubs/api/returns-arg.md description: Causes the stub to return the argument at the provided index. --- # `stub.returnsArg(index)` Causes the stub to return the argument at the provided index. `stub.returnsArg(0);` causes the stub to return the first argument. If the argument at the provided index is not available, a `TypeError` will be thrown. ```js import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.returnsArg", (t) => { const stub = sinon.stub().returnsArg(0); t.equal( stub("apple pie", "blueberry pie", "cherry pie"), "apple pie", "returns the first argument" ); t.throws( () => stub(), /returnsArg failed: 1 arguments required but only 0 present/, "throws when argument not available" ); t.end(); }); ```