如何使用Sinon正确存根返回对象的floatAPIModels?
解决Sinon存根floatAPIModels时的"Cannot stub non-existent property Person"错误
问题场景
测试insertFloatData函数时,尝试用Sinon存根floatAPIModels的Person属性,抛出错误:
TypeError: Cannot stub non-existent property Person
涉及的核心代码:
- 待测试函数
insertFloatData:
const floatAPIModels = require("models/float/floatAPIModels"); const insertFloatData = async (databaseConnection, endpoint, endpointData) => { try { const floatModel = floatAPIModels(databaseConnection); await databaseConnection.sync(); if (endpoint === "people") { endpointData.forEach(async (record) => { await floatModel.Person.upsert(record); }); } return true; } catch (error) { console.log("Unable to insert data into the database:", error); return error; } };
- 依赖的
floatAPIModels是一个返回包含Person对象的函数:
const { DataTypes } = require("sequelize"); const floatAPIModels = (sequelize) => { const Person = sequelize.define( "Person", { people_id: { type: DataTypes.INTEGER, primaryKey: true }, job_title: { type: DataTypes.STRING(200), allowNull: true }, employee_type: { type: DataTypes.BOOLEAN, allowNull: true }, active: { type: DataTypes.BOOLEAN, allowNull: true }, start_date: { type: DataTypes.DATE, allowNull: true }, end_date: { type: DataTypes.DATE, allowNull: true }, department_name: { type: DataTypes.STRING, allowNull: true }, default_hourly_rate: { type: DataTypes.FLOAT, allowNull: true }, created: { type: DataTypes.DATE, allowNull: true }, modified: { type: DataTypes.DATE, allowNull: true }, }, { timestamps: true, tableName: "Person", } ); return { Person }; }; module.exports = floatAPIModels;
错误原因
floatAPIModels本身是一个函数,而非包含Person属性的对象。你试图存根的floatAPIModels.Person根本不存在,Person是这个函数执行后返回的对象的属性,所以Sinon会抛出"无法存根不存在的属性"的错误。
正确的测试代码
需要直接存根整个floatAPIModels函数,让它在被调用时返回我们预定义好的模拟对象:
const { expect } = require("chai"); const sinon = require("sinon"); const floatAPIModels = require("src/models/float/floatAPIModels"); const floatService = require("src/services/float/floatService"); describe("insertFloatData", () => { let databaseConnection; let floatModelMock; let floatAPIModelsStub; beforeEach(() => { // 模拟数据库连接,包含sync方法的存根 databaseConnection = { sync: sinon.stub().resolves() }; // 模拟Person模型及其upsert方法 floatModelMock = { Person: { upsert: sinon.stub().resolves() }, }; // 存根整个floatAPIModels函数,让它返回我们的模拟对象 floatAPIModelsStub = sinon.stub(floatAPIModels).returns(floatModelMock); }); afterEach(() => { sinon.restore(); }); it("should insert endpointData into the 'people' endpoint", async () => { const endpoint = "people"; const endpointData = [{ record: "data" }]; await floatService.insertFloatData(databaseConnection, endpoint, endpointData); // 验证floatAPIModels被调用,且传入了数据库连接 expect(floatAPIModelsStub.calledOnceWith(databaseConnection)).to.be.true; // 验证upsert被调用一次,且传入了正确的参数 expect(floatModelMock.Person.upsert.calledOnceWith(endpointData[0])).to.be.true; }); });
额外注意点
原insertFloatData函数中存在一个潜在问题:使用forEach遍历endpointData并执行异步的upsert,但forEach不会等待所有异步操作完成。这会导致函数提前返回true,但实际可能还有upsert未执行完毕,测试也可能出现不稳定的情况。
建议将forEach改为for...of或者Promise.all:
// 方式1:用for...of(串行执行) if (endpoint === "people") { for (const record of endpointData) { await floatModel.Person.upsert(record); } } // 方式2:用Promise.all(并行执行,效率更高) if (endpoint === "people") { await Promise.all(endpointData.map(record => floatModel.Person.upsert(record))); }
内容的提问来源于stack exchange,提问作者TTBox
相关产品推荐
相关产品推荐

