Skip to content

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