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

使用Vitest模拟Sequelize模型时出现TypeError: User.findOne is not a function

问题:Vitest测试Sequelize AuthService时抛出TypeError: User.findOne is not a function

错误详情

FAIL tests/modules/auth/auth.service.test.js > AuthService > login > should login user and return token
TypeError: User.findOne is not a function

业务代码(AuthService)

const User = require('../../db/models/User');
const AuthService =  {
  doLogin: async (requestBody) => {
    const { email, password } = requestBody;
    const user = await User.findOne({ where: { email } });
    // 其余逻辑
  }
}

module.exports = AuthService;

测试代码

import { describe, it, expect, vi, afterEach } from 'vitest';
import bcrypt from 'bcryptjs';
import SequelizeMock from 'sequelize-mock';

const { faker } = require('@faker-js/faker');

const AuthService = require('../../../src/modules/auth/auth.service');
const JwtService = require('../../../src/modules/auth/jwt.service');

vi.mock('../../../src/db/models/User', async () => {
  const dbMock = new SequelizeMock();
  const fakeUser = {
    id: 1,
    phone: faker.phone.phoneNumber('##########'),
    password: faker.internet.password(8),
  };
  const UserMock = dbMock.define('User', fakeUser);
  return UserMock;
});

describe('AuthService', () => {
  afterEach(() => {
    vi.restoreAllMocks();
  });

  describe('login', () => {
    it('should login user and return token', async () => {
      // Arrange
      const requestBody = {
        phone: faker.phone.phoneNumber('##########'),
        password: faker.internet.password(8),
      };
      const fakeUser = {
        id: faker.datatype.uuid(),
        role: faker.datatype.string(4),
      };
      const fakeAccessToken = 'fake-access-token';
      vi.spyOn(bcrypt, 'compareSync').mockImplementation(() => true);
      vi.spyOn(JwtService, 'generateJWT').mockResolvedValue(fakeAccessToken);

      const expected = {
        ...fakeUser,
        accessToken: fakeAccessToken,
      };

      // Act
      const result = await AuthService.doLogin(requestBody);

      // Assert
      expect(result).toEqual(expected);
    });
  });
});

已尝试的无效方案

  • 尝试过用sequelize-mock模拟Sequelize模型的findAll、findOne方法的方案
  • 尝试过结合Jest与sequelize-mock来Mock Sequelize的方案

解决方法

1. 修正User模型的Mock导出格式

问题核心是vi.mock的返回值不符合CommonJS导出规范,原业务代码用require导入User,Mock模块需要返回正确的导出结构,同时要显式模拟findOne方法:

vi.mock('../../../src/db/models/User', async () => {
  const dbMock = new SequelizeMock();
  // 提前加密密码,匹配业务逻辑里的密码校验
  const hashedPassword = await bcrypt.hash(faker.internet.password(8), 10);
  const fakeUser = {
    id: faker.datatype.uuid(),
    email: faker.internet.email(),
    role: faker.datatype.string(4),
    password: hashedPassword,
  };
  const UserMock = dbMock.define('User', fakeUser);
  
  // 显式模拟findOne方法,返回预设的fakeUser
  UserMock.findOne = vi.fn().mockResolvedValue(fakeUser);
  
  // 返回CommonJS格式的导出
  return { default: UserMock };
});

2. 统一请求参数字段

业务代码里doLogin从请求体取email,但测试用例传的是phone,导致查询逻辑不匹配,修正测试用例的请求体:

const requestBody = {
  email: faker.internet.email(),
  password: faker.internet.password(8),
};

3. 调整测试清理逻辑

vi.restoreAllMocks()不会重置模块Mock,改用vi.clearAllMocks()清除调用记录:

afterEach(() => {
  vi.clearAllMocks();
});

4. 匹配预期返回值

确保Mock返回的用户数据包含测试用例里expected对象的所有字段(如id、role),避免断言失败。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 04:22:50