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

Nest.js单元测试Mock ClickHouse时出现Cannot read properties of undefined (reading 'toPromise')错误排查

问题分析与解决方案

你遇到的这个错误,核心原因有两个:

1. 异步测试未正确等待,导致Mock被提前清理

第二个测试用例中,你使用了.then().catch()来处理createAccount的异步操作,但没有返回这个Promise,也没有用await等待它完成。Jest会认为测试已经执行完毕,提前清理所有Mock。当后续createAccount的异步代码继续执行时,getBySlug的Mock已经失效,转而执行真实逻辑——调用this.db.query(),而你Mock的query方法默认返回undefined,自然就会抛出Cannot read properties of undefined (reading 'toPromise')的错误。

2. 测试传参错误,不符合业务逻辑

你的createAccount方法期望接收一个包含slug和name的对象参数,但测试中却传入了fakeAccount.name(一个字符串)。这会导致args.slug为undefined,虽然Mock能暂时处理,但不符合实际业务场景,也可能引发其他潜在问题。


修正后的测试代码

我帮你调整了测试用例,解决了上述问题:

import { Test, TestingModule } from '@nestjs/testing';
import { HttpException, HttpStatus } from '@nestjs/common';
import { Account } from '../accounts/entities/account.entity';
import { AccountsService } from '../accounts/accounts.service';

const now = new Date();

const fakeAccount: Account = {
  id: 'lalal',
  slug: 'palmeiras',
  name: 'Palmeiras',
  createdAt: now,
};

jest.mock('@depyronick/nestjs-clickhouse');
jest.mock('newrelic', () => ({
  recordMetric: jest.fn(),
  recordCustomEvent: jest.fn(),
}));

describe('AccountsService', () => {
  let service: AccountsService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        AccountsService,
        {
          provide: 'database',
          useValue: {
            // 提前给insert/query返回带有toPromise的对象,避免真实调用报错
            insert: jest.fn().mockReturnValue({ toPromise: jest.fn().mockResolvedValue(undefined) }),
            query: jest.fn().mockReturnValue({ toPromise: jest.fn().mockResolvedValue(null) }),
          },
        },
      ],
    }).compile();

    service = module.get<AccountsService>(AccountsService);
  });

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

  describe('creating an account', () => {
    it('should create account if not exists', async () => {
      const createArgs = { slug: fakeAccount.slug, name: fakeAccount.name };
      const insertSpy = jest.spyOn(service['db'], 'insert');

      const event = await service.createAccount(createArgs);

      expect(service.getBySlug).toHaveBeenCalledWith(createArgs.slug);
      expect(insertSpy).toHaveBeenCalledWith('accounts', expect.arrayContaining([
        expect.objectContaining({
          slug: createArgs.slug,
          name: createArgs.name,
        }),
      ]));
      expect(event.name).toEqual(createArgs.name);
      expect(event.createdAt).toEqual(now);
    });

    it('should return error if account exists', async () => {
      const createArgs = { slug: fakeAccount.slug, name: fakeAccount.name };
      // 覆盖query的返回值,模拟账号已存在
      jest.spyOn(service['db'], 'query').mockReturnValue({
        toPromise: jest.fn().mockResolvedValue(fakeAccount),
      });
      const insertSpy = jest.spyOn(service['db'], 'insert');

      // 使用expect.rejects来断言异步错误,Jest会自动等待Promise完成
      await expect(service.createAccount(createArgs)).rejects.toThrow(
        new HttpException('Account name already exists', HttpStatus.BAD_REQUEST)
      );

      expect(service.getBySlug).toHaveBeenCalledWith(createArgs.slug);
      expect(insertSpy).not.toHaveBeenCalled();
    });
  });
});

关键修改点说明

  1. 正确处理异步测试:使用await expect(...).rejects.toThrow()来断言异步错误,Jest会自动等待Promise完成,避免Mock被提前清理。
  2. 修复传参问题:给createAccount传入包含slug和name的对象,符合业务逻辑。
  3. 优化Mock方式:在beforeEach中提前给insert和query方法返回带有toPromise的Mock对象,避免真实调用时因返回undefined报错;测试时按需覆盖返回值即可。

内容的提问来源于stack exchange,提问作者Luis Novo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 00:42:42