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

NestJS中Jest单元测试报错:超出子进程异常重试限制

问题分析与解决方案

核心问题

你的测试代码存在三个关键问题,直接导致Jest worker崩溃和断言失败:

  1. 异步操作时序错误:调用usersService.update后未等待异步逻辑完成就执行断言,同时未捕获的异常直接终止worker进程
  2. 调用参数断言语法错误:错误使用toHaveBeenCalledWith传入调用顺序索引,应该用toHaveBeenNthCalledWith验证指定次数的调用参数
  3. 缺失依赖Mock:测试模块未提供Verification仓库的Mock,Service执行时调用该仓库方法会抛出未定义错误,进一步触发异常

修复步骤

1. 补充Verification仓库的Mock

在测试模块的providers中添加Verification仓库的Mock,并初始化对应的测试变量:

// 在beforeEach的createTestingModule中新增
{
  provide: getRepositoryToken(Verification),
  useValue: mockRepository(),
}

// 同时在测试变量声明中添加
let verificationsRepository: MockRepository<Verification>;
// 在beforeEach中初始化
verificationsRepository = module.get(getRepositoryToken(Verification));

2. 修正异步操作与断言顺序

将断言调整到异步操作完成后执行,确保时序正确:先验证异常,再验证调用次数和参数。

3. 修正调用参数断言语法

将toHaveBeenCalledWith替换为toHaveBeenNthCalledWith,指定第N次调用的参数:

expect(usersRepository.findOneBy).toHaveBeenNthCalledWith(1, { id });
expect(usersRepository.findOneBy).toHaveBeenNthCalledWith(2, {
  id: Not(id),
  email: newUser.email,
});

4. Mock Verification仓库的方法

为避免Service执行到verificationsRepository.create和save时出错,添加mock返回值:

verificationsRepository.create.mockReturnValue({ code: 'test-code' });
verificationsRepository.save.mockResolvedValue({ code: 'test-code' });

修改后的完整测试代码

// users.service.spec.ts
import { Test } from '@nestjs/testing';
import { UsersService } from './users.service';
import { getRepositoryToken } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { Verification } from './entities/verification.entity';
import { MailerService } from '@nestjs-modules/mailer';
import { Not, Repository } from 'typeorm';
import { HttpException, HttpStatus } from '@nestjs/common';

type MockRepository<T> = Partial<Record<keyof Repository<T>, jest.Mock>>;

const mockRepository = () => ({
  create: jest.fn(),
  delete: jest.fn(),
  findOne: jest.fn(),
  findOneBy: jest.fn(),
  save: jest.fn(),
});

const mockMailerService = () => ({
  sendMail: jest.fn(),
});

// Start test
describe('UsersService', () => {
  let usersService: UsersService;
  let mailerService: MailerService;
  let usersRepository: MockRepository<User>;
  let verificationsRepository: MockRepository<Verification>;

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      providers: [
        UsersService,
        {
          provide: getRepositoryToken(User),
          useValue: mockRepository(),
        },
        {
          provide: getRepositoryToken(Verification),
          useValue: mockRepository(),
        },
        {
          provide: MailerService,
          useValue: mockMailerService(),
        },
      ],
    }).compile();
    usersService = module.get<UsersService>(UsersService);
    mailerService = module.get<MailerService>(MailerService);
    usersRepository = module.get(getRepositoryToken(User));
    verificationsRepository = module.get(getRepositoryToken(Verification));
  });

  it('should be defined', () => {
    expect(usersService).toBeDefined();
  });

  describe('update', () => {
    const id = 1;
    const oldUser = { email: 'olduser@gmail.com', isVerified: true };
    const newUser = { email: 'newuser@gmail.com', password: 'test1234' };

    it('should fail if the user using the email already exists', async () => {
      // Mock仓库方法返回值
      usersRepository.findOneBy
        .mockResolvedValueOnce(oldUser)
        .mockResolvedValueOnce(newUser);
      verificationsRepository.create.mockReturnValue({ code: 'test-code' });
      verificationsRepository.save.mockResolvedValue({ code: 'test-code' });

      // 获取异步操作的Promise
      const updatePromise = usersService.update(id, newUser);

      // 先验证异常
      await expect(updatePromise).rejects.toEqual(
        new HttpException('The email is already taken', HttpStatus.BAD_REQUEST),
      );

      // 再验证调用次数和参数
      expect(usersRepository.findOneBy).toHaveBeenCalledTimes(2);
      expect(usersRepository.findOneBy).toHaveBeenNthCalledWith(1, { id });
      expect(usersRepository.findOneBy).toHaveBeenNthCalledWith(2, {
        id: Not(id),
        email: newUser.email,
      });
    });
  });
});

额外说明

  • 测试异步方法时,必须确保所有异步操作完成后再执行断言,否则会出现时序问题导致断言失败或未捕获异常
  • Service依赖的所有外部资源(数据库仓库、第三方服务)都需要在测试中提供Mock,避免真实调用引发错误
  • Jest worker encountered 4 child process exceptions错误通常由未捕获的异步异常导致worker崩溃,解决底层异常后该错误会自动消失

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 12:25:16