Nest应用Jest测试时Prisma外键约束(ResetPasswordToken_userId_fkey)失败求助
Nest框架功能测试中外键约束失败问题排查
问题场景
使用Test.createTestingModule构建测试应用时,执行创建重置密码令牌的测试用例出现外键约束失败错误:
Foreign key constraint failed on the field: ResetPasswordToken_userId_fkey (index)
尽管通过await获取了user对象,但创建关联该userId的重置密码令牌时,数据库中似乎不存在对应用户数据。添加短暂超时可临时解决,但并非长久方案,且该测试在GitHub Actions和本地调试时可正常运行。
测试代码如下:
describe('createResetPasswordToken', () => { it('should create reset password token based on provided data', async () => { const user = await createTestUser({ app: app(), data: { email: 'debug@user.com' } }); const data: ResetPasswordTokenDto = { userId: user.id, token: 'some-token', tokenExpiry: add(new Date(), { minutes: resetPasswordConsts.RESET_PASSWORD_TOKEN_EXPIRY_TIME_IN_MINUTES, }), }; const resetPasswordToken = await resetPasswordService.createResetPasswordToken(data); expect(resetPasswordToken).toMatchObject(data); }, 300000); });
可能原因
- 事务未提交:
createTestUser可能在事务中创建用户,但事务未完成提交,导致后续操作无法读取到用户数据。 - 应用实例不一致:每次调用
app()生成新的应用实例,导致createTestUser和resetPasswordService使用不同的数据库连接上下文,造成数据可见性问题。 - 用户保存逻辑异常:
createTestUser中的用户创建逻辑未真正将数据持久化到数据库,却提前返回了user对象。
解决方法
1. 确保用户创建事务正确提交
检查createTestUser的实现,若使用事务需确保事务完成提交。以TypeORM为例:
async function createTestUser({ app, data }) { const userService = app.get(UserService); // 事务中创建并保存用户,确保提交完成 return await userService.manager.transaction(async (entityManager) => { const user = entityManager.create(User, data); return await entityManager.save(user); }); }
若直接调用save方法,需确保await到保存操作完成,无未提交的事务包裹。
2. 复用同一个应用实例
在测试块外层初始化应用,确保整个测试用例使用同一应用上下文,避免连接上下文不一致:
describe('createResetPasswordToken', () => { let testApp: INestApplication; let resetPasswordService: ResetPasswordService; beforeAll(async () => { testApp = await app(); // 仅初始化一次应用 resetPasswordService = testApp.get(ResetPasswordService); }); afterAll(async () => { await testApp.close(); }); it('should create reset password token based on provided data', async () => { const user = await createTestUser({ app: testApp, data: { email: 'debug@user.com' } }); const data: ResetPasswordTokenDto = { userId: user.id, token: 'some-token', tokenExpiry: add(new Date(), { minutes: resetPasswordConsts.RESET_PASSWORD_TOKEN_EXPIRY_TIME_IN_MINUTES, }), }; const resetPasswordToken = await resetPasswordService.createResetPasswordToken(data); expect(resetPasswordToken).toMatchObject(data); }, 300000); });
3. 验证用户数据已持久化
在创建用户后,手动查询数据库确认用户存在,排查createTestUser逻辑是否异常:
const user = await createTestUser({ app: testApp, data: { email: 'debug@user.com' } }); // 查询数据库验证用户已保存 const savedUser = await testApp.get(UserRepository).findOneBy({ id: user.id }); expect(savedUser).toBeDefined();
若此断言失败,需重点检查createTestUser中的用户保存逻辑。
4. 调整数据库事务隔离级别
若测试数据库使用的事务隔离级别过高(如REPEATABLE READ),可能导致未提交数据不可见。可调整测试环境的隔离级别为READ COMMITTED,确保提交后的数据能被立即读取。
内容的提问来源于stack exchange,提问作者user23303868
相关产品推荐
相关产品推荐

