如何用Sinon Stub Mongoose的查询助手方法paginate?
解决Mongoose查询助手的Sinon Stub问题
问题背景
在使用Sinon测试Express/TypeScript应用时,Mongoose模型定义了查询助手方法paginate,但尝试Stub该方法时,系统提示“无法Stub不存在的属性paginate”。
模型Schema定义
import { Schema, InferSchemaType, model } from 'mongoose'; import { v4 as uuidv4 } from 'uuid'; export const articuloSchema = new Schema({ _id: { type: String, default: uuidv4 }, numeroInventario: { type: String, required: true, unique: true }, descripcion: { type: String, trim: true }, }, { versionKey: false, query: { paginate(page: number, limit: number) { return this.skip(page - 1).limit(limit); } } }); export type ArticuloType = InferSchemaType<typeof articuloSchema>; export const Articulo = model('Articulo', articuloSchema);
测试代码(原错误写法)
const getMultipleArticulos = (): ArticuloType[] => { const arr: ArticuloType[] = []; // 原代码此处类型错误,应为ArticuloType[] for (let i = 0; i < 5; i++) { arr.push({ numeroInventario: `${i}`, // 原代码此处类型不匹配,需转字符串 descripcion: 'string' }); } return arr; } it('Should return a list of items that belong to a user', function (done) { const paginateFake = sinon.fake.resolves(getMultipleArticulos()); sinon.replace(Articulo, 'paginate', paginateFake); // 错误:paginate不在模型静态属性上 chai.request(app) .get(`/api/v1/users/${userId}/articulos`) .end((err, res) => { expect(err).to.be.null; expect(res).to.have.status(200); done(); }); });
问题原因
Mongoose的查询助手方法是挂载在Query对象的原型上,而非模型(如Articulo)的静态属性。直接替换模型本身的paginate属性自然找不到对应方法,因为它根本不存在于模型实例上。
解决方案
方法1:直接Stub Query原型上的查询助手
因为查询助手最终会被添加到mongoose.Query.prototype,可以直接Stub这个原型方法:
import mongoose from 'mongoose'; // 修正测试数据生成函数的类型问题 const getMultipleArticulos = (): ArticuloType[] => { const arr: ArticuloType[] = []; for (let i = 0; i < 5; i++) { arr.push({ _id: `art-${i}`, // 补上_id,匹配Schema类型定义 numeroInventario: `${i}`, descripcion: 'string' }); } return arr; }; it('Should return a list of items that belong to a user', function (done) { // Stub Query原型上的paginate方法 const paginateStub = sinon.stub(mongoose.Query.prototype, 'paginate') .resolves(getMultipleArticulos()); chai.request(app) .get(`/api/v1/users/${userId}/articulos`) .end((err, res) => { expect(err).to.be.null; expect(res).to.have.status(200); expect(paginateStub.calledOnce).to.be.true; // 验证方法被调用 // 恢复Stub,避免污染其他测试用例 paginateStub.restore(); done(); }); });
方法2:Stub链式查询流程(更贴合业务逻辑)
如果业务代码是Articulo.find({...}).paginate(page, limit)这类链式调用,可以Stub模型的find方法,返回带有伪造paginate的对象:
it('Should return a list of items that belong to a user', function (done) { const mockQuery = { paginate: sinon.fake.resolves(getMultipleArticulos()) }; // Stub Articulo.find,返回自定义的mock查询对象 const findStub = sinon.stub(Articulo, 'find').returns(mockQuery as any); chai.request(app) .get(`/api/v1/users/${userId}/articulos`) .end((err, res) => { expect(err).to.be.null; expect(res).to.have.status(200); expect(findStub.called).to.be.true; expect(mockQuery.paginate.called).to.be.true; // 恢复Stub findStub.restore(); done(); }); });
额外注意事项
- 测试数据必须严格匹配
ArticuloType的类型定义,比如补上_id、确保numeroInventario为字符串类型; - 每次Stub后必须调用
restore(),避免影响其他测试用例; - TypeScript环境下可能需要用
as any做类型断言,解决Sinon与Mongoose类型不兼容的问题。
内容的提问来源于stack exchange,提问作者Luis Carlos Cruz Castillo
相关产品推荐
相关产品推荐

