Sinon 模拟
Spies — 观察函数调用
const sinon = require('sinon');
const spy = sinon.spy(user, 'save');
user.save({ name: '张三' });
spy.callCount; // 1
spy.calledOnce; // true
spy.calledWith({ name: '张三' }); // true
spy.firstCall.args; // [{ name: '张三' }]
spy.threw(); // 是否抛出异常
afterEach(() => {
spy.restore();
});
Stubs — 替换实现
const stub = sinon.stub(db, 'query');
stub.returns([{ id: 1, name: '张三' }]);
stub.resolves({ id: 1 }); // 异步
stub.rejects(new Error('DB 故障')); // 异步拒绝
// 条件行为
stub.withArgs('SELECT *').returns([]);
stub.withArgs('SELECT id').returns([{ id: 1 }]);
// 按调用顺序
stub.onFirstCall().returns(null);
stub.onSecondCall().returns({ id: 1 });
stub.returns({ id: 99 }); // 默认回退
stub.restore();
Mocks — 预配置期望
const mock = sinon.mock(db);
mock.expects('query')
.once()
.withArgs('SELECT * FROM users')
.returns([{ id: 1 }]);
mock.expects('close').once();
const users = db.query('SELECT * FROM users');
db.close();
mock.verify();
mock.restore();
假定时器
let clock;
beforeEach(() => {
clock = sinon.useFakeTimers(new Date('2024-01-01'));
});
afterEach(() => {
clock.restore();
});
it('延迟后触发回调', () => {
const callback = sinon.spy();
setTimeout(callback, 1000);
clock.tick(999);
assert(callback.notCalled);
clock.tick(1);
assert(callback.calledOnce);
});
Sandbox — 统一清理
let sandbox;
beforeEach(() => {
sandbox = sinon.createSandbox();
});
afterEach(() => {
// 恢复所有通过 sandbox 创建的 stub/spy/mock
sandbox.restore();
});
it('获取用户列表', async () => {
const stub = sandbox.stub(db, 'query').resolves([{ id: 1 }]);
sandbox.spy(logger, 'info');
const users = await service.getAll();
assert(stub.calledOnce);
});
sinon-chai 集成
const sinonChai = require('sinon-chai');
chai.use(sinonChai);
const { expect } = chai;
it('stub 调用正确', () => {
expect(stub).to.have.been.calledOnce;
expect(stub).to.have.been.calledWith('/users', { name: '张三' });
expect(stub).to.not.have.been.calledTwice;
});