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

Jest测试报错:TypeError:service(...).returns不是函数(Nest.js+TypeORM)

Jest单元测试报错:TypeError: contractService.getContractStatusEditable(...).returns is not a function

在基于Nest.js和TypeORM开发的服务中,编写assignUsersToContracts方法的Jest单元测试时,出现上述TypeError。部分测试用例预期捕获GeneralApplicationException,实际却捕获到TypeError。


服务代码

@EnableLog()
public async assignUsersToContracts(
  id: string,
  contractUsers: ContractUserInput[],
  authUser: any,
): Promise<string> {
  const contract = await this.contractService.findOneOrFail({
    where: { id },
  });
  const statusEditable = this.contractService.getContractStatusEditable();
  if (!statusEditable.includes(contract.status)) {
    throw new GeneralApplicationException(
      'Users cannot be assigned at this stage',
    );
  }
  const assignUsers: ContractUser[] = [];
  if (contract.assignedUsers) {
    for (const item of contract.assignedUsers) {
      await this.contractUserRepo.softDelete(item.id);
    }
  }
  for (const contractUser of contractUsers) {
    const newUser = await this.userService.getOrCreate(
      { email: contractUser.email },
      {
        email: contractUser.email,
        firstName: contractUser.firstName,
      },
    );
    if (!newUser)
      throw new GeneralApplicationException(
        'Not able to share to the user for now',
      );
    assignUsers.push({
      userId: newUser.id,
      role: contractUser.role,
    } as CoUser);
  }
  await this.contractUserRepo.save(assignUsers);
  return 'Successfull';
}

测试代码

describe('assignUsersToContracts', () => {
  it('should throw an error when contract is not editable ', async () => {
    try {
      contractRepo.findOne.mockResolvedValueOnce(newContract);
      contractService
        .findOneOrFail(Arg.any())
        .returns(Promise.resolve(newContract));
      contractService
        .getContractStatusEditable()
        .returns(await Promise.resolve(editContractStatus));
      await contractUserService.assignUsersToContracts(
        newContract.id,
        contractUserInputList,
        authUser,
      );
    } catch (error) {
      expect(error).toBeInstanceOf(GeneralApplicationException);
    }
  });
  it('should throw an error when a user is undefined ', async () => {
    try {
      contractRepo.findOne.mockResolvedValueOnce(newEditableContract);
      contractService
        .findOneOrFail(Arg.any())
        .returns(Promise.resolve(newEditableContract));
      contractService
        .getContractStatusEditable()
        .returns(await Promise.resolve(editContractStatus));
      contractUserRepository.softDelete.mockResolvedValueOnce(deleteResult);
      userService
        .getOrCreate(Arg.any(), Arg.any())
        .returns(Promise.resolve(undefined));
      await contractUserService.assignUsersToContracts(
        newEditableContract.id,
        contractUserInputList,
        authUser,
      );
    } catch (error) {
      expect(error).toBeInstanceOf(GeneralApplicationException);
    }
  });
  it('should save a user to a Contract', async () => {
    try {
      contractRepo.findOne.mockResolvedValueOnce(newEditableContract);
      contractService
        .findOneOrFail(Arg.any())
        .returns(Promise.resolve(newEditableContract));
      contractService
        .getContractStatusEditable()
        .returns(await Promise.resolve(editContractStatus));
      contractUserRepository.softDelete.mockResolvedValueOnce(deleteResult);
      userService
        .getOrCreate(Arg.any(), Arg.any())
        .returns(Promise.resolve(userResponse.user));
      contractUserRepository.save.mockResolvedValueOnce(userResponse);
      const res = await contractUserService.assignUsersToContracts(
        newContract.id,
        contractUserInputList,
        authUser,
      );
      expect(res).toEqual('Successfull');
    } catch (error) {
      console.log(error);
    }
  });
});

报错信息

● Test ContractUserService › assignUsersToContracts › should throw an error when a user is undefined 

  expect(received).toBeInstanceOf(expected)

  Expected constructor: GeneralApplicationException
  Received constructor: TypeError

    315 |         );
    316 |       } catch (error) {
  > 317 |         expect(error).toBeInstanceOf(GeneralApplicationException);
        |                       ^
    318 |       }
    319 |     });
    320 |     it('should save a user to a Contract', async () => {

 console.log
  TypeError: contractService.getContractStatusEditable(...).returns is not a function
      at Object.<anonymous> (/t/service/contract.user.service.test.ts:328:12)
      at processTicksAndRejections (internal/process/task_queues.js:95:5)


Test Suites: 1 failed, 1 total

问题原因与修复方案

原因

getContractStatusEditable是同步方法(服务代码中直接调用,无await),但测试代码错误地使用了Jest异步mock的.returns()方法。.returns()仅适用于异步mock函数的链式调用,同步mock需直接设置返回值。

修复步骤

  1. 修正同步方法的mock写法:
    替换错误的.returns()调用,改用mockReturnValue:

    // 错误写法
    contractService.getContractStatusEditable().returns(await Promise.resolve(editContractStatus));
    // 正确写法(若用spyOn)
    jest.spyOn(contractService, 'getContractStatusEditable').mockReturnValue(editContractStatus);
    // 若contractService本身是mock实例
    contractService.getContractStatusEditable.mockReturnValue(editContractStatus);
    
  2. 统一异步方法的mock规范:
    findOneOrFail是异步方法,正确mock方式如下:

    // 错误写法
    contractService.findOneOrFail(Arg.any()).returns(Promise.resolve(newContract));
    // 正确写法
    jest.spyOn(contractService, 'findOneOrFail').mockResolvedValue(newContract);
    
  3. 优化错误捕获逻辑:
    用expect().rejects.toThrow()替代try-catch,更简洁可靠:

    it('should throw an error when contract is not editable ', async () => {
      jest.spyOn(contractService, 'findOneOrFail').mockResolvedValue(newContract);
      jest.spyOn(contractService, 'getContractStatusEditable').mockReturnValue(editContractStatus);
      
      await expect(contractUserService.assignUsersToContracts(
        newContract.id,
        contractUserInputList,
        authUser,
      )).rejects.toBeInstanceOf(GeneralApplicationException);
    });
    

内容的提问来源于stack exchange,提问作者Anto P V

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 20:11:09