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

如何在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);
    });
  });
});

关键说明

  1. Stub实例方法而非类方法:因为createNormal调用的是this上的实例方法,所以我们直接StubuserRepo实例上的方法,而不是UserRepository类本身的方法。
  2. 测试场景覆盖:分别测试了「无推荐码」和「有推荐码」两种情况,确保createNormal的分支逻辑都被覆盖。
  3. 双重验证:不仅验证返回结果,还验证依赖方法的调用次数和参数,确保业务逻辑的执行流程符合预期。

内容的提问来源于stack exchange,提问作者IdontEvenEven

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 17:50:22