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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 04:18:21