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

NestJS单元测试中Mock newrelic模块失效问题排查

NestJS 集成 NewRelic 单元测试报错解决方案

为什么测试环境下 NewRelic 无法找到对应配置文件

核心原因有三点:

  • NewRelic Node Agent 初始化逻辑是:第一次被 import/require 时会立刻执行初始化流程,默认从执行命令的工作目录(也就是跑npm run test时的项目根目录) 查找名为 newrelic.js 的配置文件,不会递归扫描/src这类子目录,也默认不识别.ts后缀的配置文件,找不到配置就会抛出未设置app_name的错误。
  • 应用正常运行时,你在入口文件main.ts最顶部先导入了/src/newrelic.ts自定义配置,之后才加载业务模块,NewRelic 能提前拿到配置参数所以运行正常。但跑单元测试时,Jest 会直接加载被测试的AccountsService文件,文件顶部直接导入了newrelic,这时候 NewRelic 还没拿到自定义配置就先触发了初始化逻辑。
  • 你写的jest.mock位置错误:写在describe块内部的 mock 不会被 Jest 提升到文件顶部执行,真实的 NewRelic 模块会在 mock 生效前就被加载初始化,自然走了默认的配置查找逻辑,触发报错。

Mock NewRelic 模块的正确实现方式

方案1:单文件内正确Mock(适配单个测试文件快速修复)

把jest.mock调用移到测试文件顶层,所有 import 语句之后、describe块之前。Jest 会自动把顶层的jest.mock提升到整个文件最顶部执行,确保所有业务模块导入 NewRelic 之前,就已经把模块替换为 Mock 版本,不会触发真实初始化:

// 先放所有业务相关的import
import { getModelToken } from '@nestjs/mongoose';
import { Test, TestingModule } from '@nestjs/testing';
import { Account } from '../entities/account.entity';
import { AccountsController } from '../accounts.controller';
import { AccountsService } from '../accounts.service';
import { CreateAccountData } from '../dto/create-account-data';

// 顶层写jest.mock,不要放在describe里
jest.mock('newrelic', () => ({
  recordMetric: jest.fn(),
  recordCustomEvent: jest.fn(),
  noticeError: jest.fn(),
  // 按需补全项目中用到的其他NewRelic方法
}));

// mock注册完成后再导入newrelic,拿到的是mock实例
import newrelic from 'newrelic';

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

  const now = new Date();
  const fakeAccount: Account = {
    _id: 'lalal',
    name: 'Palmeiras',
    createdAt: now,
  };
  const postBody: CreateAccountData = {
    name: 'Palmeiras',
  };

  class accountModel {
    constructor(private data) {}
    save = jest.fn().mockResolvedValue(this.data);
    static find = jest.fn().mockResolvedValue([fakeAccount]);
    static findOne = jest.fn().mockResolvedValue(fakeAccount);
    static create = jest.fn().mockResolvedValue(fakeAccount);
    static findOneAndUpdate = jest.fn().mockResolvedValue(fakeAccount);
    static deleteOne = jest.fn().mockResolvedValue(true);
  }

  beforeEach(async () => {
    // 每个用例前清空mock调用记录,避免用例间干扰
    jest.clearAllMocks();
    const module: TestingModule = await Test.createTestingModule({
      controllers: [AccountsController],
      providers: [
        AccountsService,
        {
          provide: getModelToken(Account.name),
          useValue: accountModel,
        },
      ],
    }).compile();

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

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

  describe('create account POST /', () => {
    it('should call the right service', async () => {
      const method = jest.spyOn(service, 'createAccount').mockReturnValue(Promise.resolve(fakeAccount));
      await controller.createAccount(postBody);
      // 注意断言参数要和业务实际传参一致,原测试里写的'hello',1是错误参数
      expect(newrelic.recordCustomEvent).toHaveBeenCalledWith('accountCreated', expect.objectContaining({
        accountId: expect.anything(),
        groupName: 'Palmeiras'
      }));
      expect(newrelic.recordMetric).toHaveBeenCalledWith('accounts_created', 1);
      expect(method).toHaveBeenCalledWith(postBody.name);
    });
  });
});

方案2:全局Mock(推荐,一劳永逸适配所有测试文件)

在项目根目录新建__mocks__文件夹,在文件夹内新建newrelic.ts写入通用Mock逻辑:

// __mocks__/newrelic.ts
export default {
  recordMetric: jest.fn(),
  recordCustomEvent: jest.fn(),
  noticeError: jest.fn(),
  startSegment: jest.fn(),
  startWebTransaction: jest.fn(),
  endTransaction: jest.fn(),
  addCustomAttribute: jest.fn(),
  // 按需补全项目用到的其他NewRelic方法
};

之后在Jest配置文件(jest.config.js或package.json的jest字段)对应的setupFilesAfterEnv指向的setup文件里,加一行全局注册:

// jest.setup.ts
jest.mock('newrelic');

这样所有测试文件运行前都会自动把NewRelic替换为Mock版本,完全不会触发真实模块初始化,从根源上避免配置找不到的报错,也不用每个测试文件重复写Mock逻辑。

方案3:测试环境禁用NewRelic(不想写Mock时可选)

如果不想Mock模块,可以在测试环境的环境变量里添加以下配置,让NewRelic自动跳过初始化和校验:

NEW_RELIC_ENABLED=false
NEW_RELIC_APP_NAME=test-service
NEW_RELIC_LICENSE_KEY=test

这种方式会加载真实的NewRelic模块,测试启动速度会比Mock方案慢一点,适合需要验证NewRelic调用链路的场景。

补充提示:NewRelic默认要求配置文件放在项目根目录、命名为newrelic.js,你现在把配置放在/src/newrelic.ts属于非标准路径,后续构建、部署时也容易出现配置丢失的问题,建议移到根目录改成.js格式,所有环境都能自动识别配置。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 11:24:22