You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 15:30:43