docs/concepts/stubs/migrating-to-fakes.md
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.
Stubs have unique features that fakes don't provide:
onCall(), onFirstCall(), onSecondCall(), onThirdCall()withArgs() for different returns per arguments.get(), .set(), .value() for property accessorsFor these cases, continue using stubs. For everything else, prefer fakes.
Before (Stub):
const stub = sinon.stub().returns(42);
const result = stub();
assert.equal(result, 42);
After (Fake):
const fake = sinon.fake.returns(42);
const result = fake();
assert.equal(result, 42);
Change: Replace sinon.stub().returns() with sinon.fake.returns()
Before (Stub):
const stub = sinon.stub().returnsArg(0);
stub("hello"); // returns 'hello'
stub("world"); // returns 'world'
After (Fake):
// 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
Before (Stub):
const stub = sinon.stub().throws(new Error("Failed"));
assert.throws(() => stub());
After (Fake):
const fake = sinon.fake.throws(new Error("Failed"));
assert.throws(() => fake());
Change: Replace sinon.stub().throws() with sinon.fake.throws()
Before (Stub):
const stub = sinon.stub().resolves("success");
const result = await stub();
assert.equal(result, "success");
After (Fake):
const fake = sinon.fake.resolves("success");
const result = await fake();
assert.equal(result, "success");
Change: Replace sinon.stub().resolves() with sinon.fake.resolves()
Before (Stub):
const stub = sinon.stub().rejects(new Error("Failed"));
try {
await stub();
} catch (e) {
assert.equal(e.message, "Failed");
}
After (Fake):
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()
Before (Stub):
const stub = sinon.stub().callsFake(function (x) {
return x * 2;
});
After (Fake):
const fake = sinon.fake(function (x) {
return x * 2;
});
Change: Pass function directly to sinon.fake() instead of chaining .callsFake()
Before (Stub):
const stub = sinon.stub().yields("error", "data");
stub((err, data) => {
assert.equal(err, "error");
assert.equal(data, "data");
});
After (Fake):
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()
Before (Stub):
const stub = sinon.stub().yieldsAsync("error", "data");
// callback invoked asynchronously
After (Fake):
const fake = sinon.fake.yieldsAsync("error", "data");
// callback invoked asynchronously
Change: Replace sinon.stub().yieldsAsync() with sinon.fake.yieldsAsync()
Before (Stub):
const obj = {
method() {
return "original";
}
};
sinon.stub(obj, "method").returns("stubbed");
obj.method(); // 'stubbed'
After (Fake):
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
Some scenarios require call-specific behavior which fakes don't support:
// 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:
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
Returning different values based on arguments requires stubs:
// 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:
const fake = sinon.fake((food) => {
if (food === "apple") return "fruit";
if (food === "carrot") return "vegetable";
return "unknown";
});
Stubbing property getters/setters requires stubs:
// 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.
Stubs (Mutable):
const stub = sinon.stub();
stub.returns(1); // Changes behavior
stub(); // 1
stub.returns(2); // Changes behavior again
stub(); // 2
Fakes (Immutable):
const fake1 = sinon.fake.returns(1);
const fake2 = sinon.fake.returns(2);
fake1(); // 1
fake2(); // 2
// fake1 still returns 1, fake2 returns 2
Stubs (Chained):
const stub = sinon.stub().onFirstCall().returns(1).onSecondCall().returns(2);
Fakes (No Chaining):
// Behavior defined at creation
const fake = sinon.fake.returns(1);
// No way to change behavior
Stubs (Combined):
// Stub creation and plugging in are combined
sinon.stub(obj, "method").returns(42);
Fakes (Separated):
// Creation and plugging in are separate
const fake = sinon.fake.returns(42);
sinon.replace(obj, "method", fake);
sinon.stub() usageonCall, onFirstCall, etc.) - keep as stubwithArgs) - keep as stub.get(), .set(), .value()) - keep as stubstub().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)sinon.replace()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! ✨