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; });