docs/concepts/stubs/error-handling.md
Stubs validate their usage and throw errors when used incorrectly. Understanding these errors helps you use stubs properly.
Stubs cannot be created on ES Modules because their exports are read-only.
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:
import { someMethod } from "./my-module.js";
// Create a wrapper object to stub
const wrapper = { someMethod };
sinon.stub(wrapper, "someMethod");
Or use sinon.replace():
import * as myModule from "./my-module.js";
import * as sinon from "sinon";
const fake = sinon.fake.returns("mocked value");
sinon.replace(myModule, "someMethod", fake);
The three-argument form stub(obj, 'method', func) has been removed in modern Sinon versions.
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:
const obj = {
method() {
return "original";
}
};
// Correct approach
sinon.stub(obj, "method").callsFake(function () {
return "replacement";
});
Attempting to stub a property that doesn't exist will fail.
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():
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());
Stubs can only replace functions, not regular properties.
const obj = { name: "Alice" };
// This will throw an error
sinon.stub(obj, "name");
Solution: Use sinon.stub().get() or sinon.stub().value() for properties:
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");
Forgetting to restore stubs can cause test pollution.
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:
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();
});
Some behavior changes only affect future calls, not past ones.
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.
Using multiple behavior methods can be confusing:
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:
const stub = sinon
.stub()
.onFirstCall()
.returns("A")
.onSecondCall()
.returns("B");
stub(); // 'A'
stub(); // 'B'
afterEach(() => sinon.restore()) in testsonCall() for sequential, withArgs() for conditionalcallsFake() - More flexible than old three-argument form.get(), .set(), or .value() for properties