如何在Node.js仓库测试中Stub其他仓库调用及数据库操作?
没问题,我来帮你搞定这个Stub的问题!要测试createNormal这种依赖多个内部方法和数据库操作的函数,我们可以用Sinon把所有外部依赖和内部调用都Mock/Stub掉,这样就能专注测试createNormal本身的业务逻辑了。
核心思路
我们需要Stub三类东西:
- pg-promise的数据库实例方法(
db.one、db.oneOrNone),避免真实操作数据库 - UserRepository内部的异步方法(
generateToken、generateUniqueRefToken、findByRefToken),因为这些是createNormal直接调用的依赖 - 还要注意测试前后的Stub清理,避免不同测试用例互相污染
完整测试代码(带注释)
const chai = require("chai"); const sinon = require("sinon"); const expect = chai.expect; const { faker } = require("@faker-js/faker"); const UserRepository = require("../../repos/user"); // 记得引入CREATE_NORMAL_USER常量(假设它来自你的常量文件) const { CREATE_NORMAL_USER } = require("../../repos/constants"); describe("UserRepository", function() { // 每个测试用例执行前,初始化Stub和仓库实例 beforeEach(function() { // 1. Stub pg-promise的数据库实例 this.stubbedDb = { // 模拟插入用户后返回的结果(包含新用户ID) one: sinon.stub().resolves({ id: faker.datatype.uuid() }), // 默认模拟找不到推荐用户,后面可以按需修改 oneOrNone: sinon.stub().resolves(null) }; // 2. 创建UserRepository实例,传入Stubbed的数据库 this.userRepo = new UserRepository(this.stubbedDb); // 3. Stub仓库内部的异步方法 sinon.stub(this.userRepo, "generateToken").resolves(faker.random.alphaNumeric(12)); sinon.stub(this.userRepo, "generateUniqueRefToken").resolves(faker.random.alphaNumeric(8)); // 单独保存findByRefToken的Stub,方便后续用例修改返回值 this.findByRefTokenStub = sinon.stub(this.userRepo, "findByRefToken").resolves(null); }); // 每个测试用例执行后,恢复所有Stub,避免污染后续测试 afterEach(function() { sinon.restore(); }); describe("createNormal", function () { it("should create a normal user without referral info", async function () { // 准备测试数据(不含referred字段) const testData = { userId: faker.datatype.uuid(), firstName: faker.name.firstName(), lastName: faker.name.lastName(), username: faker.internet.userName(), password: faker.internet.password(), email: faker.internet.email(), dob: faker.date.birthdate(), gender: faker.name.gender(), country: faker.address.country(), phone: faker.phone.number() }; // 调用被测方法 const result = await this.userRepo.createNormal(testData); // 验证返回结果是否符合预期 expect(result.id).to.exist; expect(result.firstName).to.equal(testData.firstName); expect(result.email).to.equal(testData.email); expect(result.verifyToken).to.exist; expect(result.refToken).to.exist; expect(result.referredBy).to.be.null; // 验证内部方法的调用逻辑 expect(this.userRepo.generateToken.calledOnce).to.be.true; expect(this.userRepo.generateUniqueRefToken.calledOnce).to.be.true; expect(this.findByRefTokenStub.called).to.be.false; // 无推荐码,不该调用此方法 // 验证数据库插入的参数是否正确 expect(this.stubbedDb.one.calledOnceWith(CREATE_NORMAL_USER, sinon.match.object)).to.be.true; const insertedUser = this.stubbedDb.one.firstCall.args[1]; expect(insertedUser.first_name).to.equal(testData.firstName.trim()); expect(insertedUser.email).to.equal(testData.email.trim().toLowerCase()); expect(insertedUser.meta.referred).to.be.null; }); it("should create a normal user with valid referral", async function () { // 准备带推荐码的测试数据 const testData = { userId: faker.datatype.uuid(), firstName: faker.name.firstName(), lastName: faker.name.lastName(), username: faker.internet.userName(), password: faker.internet.password(), email: faker.internet.email(), dob: faker.date.birthdate(), referred: faker.random.alphaNumeric(8) }; // 修改findByRefToken的Stub,模拟找到推荐用户 const referredUser = { id: faker.datatype.uuid() }; this.findByRefTokenStub.resolves(referredUser); // 调用被测方法 const result = await this.userRepo.createNormal(testData); // 验证推荐用户信息被正确返回 expect(result.referredBy).to.deep.equal(referredUser); // 验证findByRefToken的调用参数正确 expect(this.findByRefTokenStub.calledOnceWith(testData.referred)).to.be.true; // 验证数据库插入的meta中referred字段正确 const insertedUser = this.stubbedDb.one.firstCall.args[1]; expect(insertedUser.meta.referred).to.equal(referredUser.id); }); }); });
关键说明
- Stub实例方法而非类方法:因为
createNormal调用的是this上的实例方法,所以我们直接StubuserRepo实例上的方法,而不是UserRepository类本身的方法。 - 测试场景覆盖:分别测试了「无推荐码」和「有推荐码」两种情况,确保
createNormal的分支逻辑都被覆盖。 - 双重验证:不仅验证返回结果,还验证依赖方法的调用次数和参数,确保业务逻辑的执行流程符合预期。
内容的提问来源于stack exchange,提问作者IdontEvenEven
相关产品推荐
相关产品推荐

