使用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
相关产品推荐
相关产品推荐

