docs/guides/how-to/stub-dependency.md
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 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, neither in the module under test nor in the test.
dependencyModule.jsfunction getSecretNumber() {
return 44;
}
module.exports = {
getSecretNumber,
};
moduleUnderTest.jsconst dependencyModule = require("./dependencyModule");
function getTheSecret() {
return `The secret was: ${dependencyModule.getSecretNumber()}`;
}
module.exports = {
getTheSecret,
};
test.jsconst 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");
});
});
});
In some cases you might need to stub a dependency that returns a promise. To test it you can add the async keyword to the test method and call the method being tested with the await keyword.
userApi.jsconst 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,
};
userUtils.jsconst 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,
};
UserUtils-test.jsconst 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);
});
});
});