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.
// This will throw: "object is null"
sinon.mock(null);
sinon.mock(undefined);Solution: Provide a valid object:
const obj = { method() {} };
const mock = sinon.mock(obj);Falsy Method Name
Setting an expectation on a falsy method name will fail.
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:
mock.expects("validMethodName");Unmet Expectations
The most common mock error: calling verify() when expectations aren't met.
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:
const obj = { greet() {} };
const mock = sinon.mock(obj);
mock.expects("greet").once();
obj.greet(); // Call the method
mock.verify(); // Now succeedsUnexpected Calls
Calling a mocked method in a way that doesn't match any expectations will fail immediately.
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:
// 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().
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 mockThis 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:
mock.verify(); // First call succeeds and restores
mock.verify(); // Second call throws - mock is already restoredSolution: Only call verify() once per mock, typically in test cleanup:
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.
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:
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 expectationToo Many Calls
Calling a method more times than expected will fail.
const mock = sinon.mock(obj);
mock.expects("method").once();
obj.method(); // OK
obj.method(); // Throws: "Unexpected call: method()"Solution: Adjust expectation or use atLeast():
// 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 2Debugging Mock Failures
When mock expectations fail, the error messages can be dense. Here's how to read them:
Error: Expected greet('[...]') once (never called)
greet(Bob) at MyTest.js:15Reading the error:
- "Expected greet('[...]') once" - The expectation
- "(never called)" - What actually happened
- "greet(Bob) at MyTest.js:15" - Stack trace of unexpected calls (if any)
Debugging steps:
- Check the expectation: Is it correct?
- Check the actual calls: Are they happening?
- Check the arguments: Do they match?
- Check the call count: Right number of calls?
Best Practices
- One mock per test - Multiple mocks make failures hard to diagnose
- Verify once - Call
verify()only once, in cleanup - Expect only what matters - Don't mock what you don't need to verify
- Use explicit assertions - Consider fakes + assertions instead of mocks
- Test behavior, not implementation - Avoid coupling tests to internal calls
- Clear error messages - Use descriptive method names in tests
Common Pitfalls
Testing Implementation Details
// 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 callsToo Many Expectations
// 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
// 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);