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

如何测试基于Sequelize的postMemberController数据库操作函数?

问题分析与测试方案修正

你的测试失败核心原因是Jest Mock配置错误,包括Mock位置、模型名称不匹配、缺失必要依赖Mock等问题,以下是具体修正方案:

核心问题点

  1. Mock位置错误:jest.mock需放在测试文件顶部(模块导入前),否则无法正确替换原DB模块。
  2. 模型名称不匹配:控制器中用db.facility,但你Mock的是复数facilities;db.card对应define('card')而非dbMock.card。
  3. 缺失必要模型Mock:控制器用到db.user,但你的Mock中未定义该模型。
  4. 未Mock外部依赖:axios、tokenCreator、firebase.createCustomToken等外部调用未Mock,会导致测试执行真实逻辑报错。
  5. Response对象未模拟断言能力:普通对象无法验证方法调用情况,需用jest.fn()模拟。

修正后的完整测试代码

const { postMemberController } = require('../path/to/your/controller');
const SequelizeMock = require('sequelize-mock');
const axios = require('axios');
const tokenCreator = require('../path/to/tokenCreator');
const adminFirebase = require('../path/to/adminFirebase');
const generateUUID = require('../path/to/generateUUID');

// 1. 顶部Mock DB模块,确保导入时生效
jest.mock('../db/models/index.js', () => {
  const dbMock = new SequelizeMock();
  // 模型名称必须和控制器中db.xxx完全一致
  const facility = dbMock.define('facility', { id: '6-30189' });
  const member = dbMock.define('member', {});
  const card = dbMock.define('card', {});
  const user = dbMock.define('user', { user_id: 'mock-admin-id' });

  // 模拟Sequelize事务
  dbMock.sequelize = {
    transaction: jest.fn(callback => callback()) // 直接执行事务回调,无需测试事务本身
  };

  // 返回完整的Mock DB对象
  return { facility, member, card, user, sequelize: dbMock.sequelize };
});

// 2. Mock所有外部依赖
jest.mock('axios');
jest.mock('../path/to/tokenCreator', () => jest.fn(() => 'mock-jwt-token'));
jest.mock('../path/to/adminFirebase', () => ({
  auth: { createCustomToken: jest.fn(() => 'mock-firebase-token') }
}));
jest.mock('../path/to/generateUUID', () => jest.fn(() => 'mock-uuid'));

// 测试用工具函数与数据
function makeId() {
  return Math.random().toString(36).substring(2, 10);
}

const card_number = 11111111111111;
const card_id = makeId();
const facility_id = '6-30189';

const testMember = {
  from: 'central',
  card_number,
  id: makeId(),
  first_name: 'Demo',
  last_name: 'Demo',
  id_no: card_number,
  card_id,
  facility_id,
  birthplace: 'Gölbaşı/Ankara',
  birthdate: new Date().toISOString(),
  is_employee: false,
  creator: makeId(),
  updater: makeId()
};

// 测试套件
describe('postMemberController', () => {
  let req, res;

  // 每个测试前重置Mock与请求对象
  beforeEach(() => {
    jest.clearAllMocks();
    req = {
      decodedToken: { user_id: makeId() },
      body: { ...testMember }
    };
    // 模拟支持链式调用的Response对象
    res = {
      sendStatus: jest.fn(),
      send: jest.fn(),
      status: jest.fn(() => res),
      json: jest.fn(() => res)
    };
  });

  // 测试场景1:无权限时返回401
  it('returns 401 when no valid user in decodedToken', async () => {
    req.decodedToken = {};
    await postMemberController(req, res);
    expect(res.sendStatus).toHaveBeenCalledWith(401);
  });

  // 测试场景2:设施不存在时返回提示
  it('sends "no facilities" when target facility not found', async () => {
    const db = require('../db/models/index.js');
    db.facility.findOne.mockResolvedValue(null);
    
    await postMemberController(req, res);
    expect(res.send).toHaveBeenCalledWith("no facilities");
  });

  // 测试场景3:会员不存在时创建新会员与卡片
  it('creates new member and card when member does not exist', async () => {
    const db = require('../db/models/index.js');
    // 模拟设施存在
    db.facility.findOne.mockResolvedValue({ id: facility_id });
    // 模拟会员不存在
    db.member.findOne.mockResolvedValue(null);
    // 模拟管理员存在
    db.user.findOne.mockResolvedValue({ user_id: 'mock-admin-id' });
    // 模拟会员创建结果
    const mockCreatedMember = {
      dataValues: { ...testMember, id: makeId(), firebase_token: 'mock-firebase-token' }
    };
    db.member.create.mockResolvedValue(mockCreatedMember);
    // 模拟卡片不存在,需创建新卡
    db.card.findOne.mockResolvedValue(null);

    await postMemberController(req, res);

    expect(db.member.create).toHaveBeenCalled();
    expect(db.card.create).toHaveBeenCalled();
    expect(res.status).toHaveBeenCalledWith(200);
    expect(res.json).toHaveBeenCalled();
    // 因为from为'central',断言axios不会被调用
    expect(axios.post).not.toHaveBeenCalled();
  });

  // 测试场景4:会员存在时更新会员信息
  it('updates existing member when member already exists', async () => {
    const db = require('../db/models/index.js');
    // 模拟设施存在
    db.facility.findOne.mockResolvedValue({ id: facility_id });
    // 模拟已有会员
    const existingMember = {
      id_no: testMember.id_no,
      facility_id: ['old-facility-id'],
      id: makeId()
    };
    db.member.findOne.mockResolvedValue(existingMember);
    // 模拟管理员存在
    db.user.findOne.mockResolvedValue({ user_id: 'mock-admin-id' });
    // 模拟会员更新成功
    db.member.update.mockResolvedValue([1]);

    await postMemberController(req, res);

    expect(db.member.update).toHaveBeenCalledWith(
      expect.objectContaining({
        facility_id: expect.arrayContaining(['old-facility-id', facility_id])
      }),
      expect.objectContaining({ where: { id_no: existingMember.id_no } }),
      expect.any(Object)
    );
    expect(res.status).toHaveBeenCalledWith(200);
    expect(res.json).toHaveBeenCalled();
  });
});

关键说明

  1. Mock一致性:所有模型名称必须和控制器中db.xxx的命名完全一致(比如控制器用db.facility,Mock时就用define('facility'))。
  2. 事务模拟:直接让sequelize.transaction执行传入的回调,无需测试Sequelize事务本身,聚焦业务逻辑。
  3. 外部依赖隔离:所有第三方调用(axios、token生成等)都用Mock替换,避免测试依赖外部服务或环境。
  4. 场景覆盖:覆盖权限不足、设施不存在、会员创建、会员更新四个核心分支,确保逻辑完整性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 12:01:12