使用Nestjs测试控制器时嘲笑NewRelic模块

发布于 2025-02-06 03:26:09 字数 6979 浏览 2 评论 0原文

我刚刚在Nestjs应用程序上安装了NewRelic,它工作正常。但是现在执行测试时正在抛出异常:

console.error 错误:新遗物要求您命名此应用程序! 在您的newRelic.js文件或设置环境变量中设置app_name new_relic_app_name。 没有启动!

在测试下,NewRelic模块在/src/newrelic.ts下找不到配置时,似乎

。新遗物本身正在应用程序中工作。它只是在打破测试。

这是 /src下的我的newRelic.ts文件:

'use strict'
/**
 * New Relic agent configuration.
 *
 * See lib/config/default.js in the agent distribution for a more complete
 * description of configuration variables and their potential values.
 */
exports.config = {
  /**
   * Array of application names.
   */
  app_name: ['product-metrics-dev'],
  /**
   * Your New Relic license key.
   */
  license_key: 'redacted',
  logging: {
    /**
     * Level at which to log. 'trace' is most useful to New Relic when diagnosing
     * issues with the agent, 'info' and higher will impose the least overhead on
     * production applications.
     */
    level: 'info'
  },
  /**
   * When true, all request headers except for those listed in attributes.exclude
   * will be captured for all traces, unless otherwise specified in a destination's
   * attributes include/exclude lists.
   */
  allow_all_headers: true,
  application_logging: {
    forwarding: {
      /**
       * Toggles whether the agent gathers log records for sending to New Relic.
       */
      enabled: true
    }
  },
  attributes: {
    /**
     * Prefix of attributes to exclude from all destinations. Allows * as wildcard
     * at end.
     *
     * NOTE: If excluding headers, they must be in camelCase form to be filtered.
     *
     * @env NEW_RELIC_ATTRIBUTES_EXCLUDE
     */
    exclude: [
      'request.headers.cookie',
      'request.headers.authorization',
      'request.headers.proxyAuthorization',
      'request.headers.setCookie*',
      'request.headers.x*',
      'response.headers.cookie',
      'response.headers.authorization',
      'response.headers.proxyAuthorization',
      'response.headers.setCookie*',
      'response.headers.x*'
    ]
  }
}

这是一个简单的服务,正在运行一些新代码:

import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Account } from './entities/account.entity';
import { v4 as uuidv4 } from 'uuid';
import { ObjectID } from 'bson';
import * as newrelic from 'newrelic';

@Injectable()
export class AccountsService {
    private readonly logger = new Logger('Accounts Service');
    constructor(@InjectModel(Account.name) private readonly accountModel: Model<Account>) {}

    async createAccount(name): Promise<Account> {
        //checks if name already exists
        const acc: Account = await this.accountModel.findOne({ name });

        if (acc) {
            throw new HttpException('Account name already exists', HttpStatus.BAD_REQUEST);
        }

        const now = new Date();
        const id = new ObjectID();
        const account = await this.accountModel.create({ _id: id, name, createdAt: now });
        newrelic.recordMetric('accounts_created', 1);
        newrelic.recordCustomEvent('accountCreated', { accountId: id, groupName: account.name });

        return account;
    }

    async getById(accountId: string): Promise<Account> {
        return await this.accountModel.findOne({ _id: accountId });
    }
}

和调用它的控制器:

import { Body, Controller, Logger, Post } from '@nestjs/common';
import { AccountsService } from './accounts.service';
import { CreateAccountData } from './dto/create-account-data';

@Controller('accounts')
export class AccountsController {
    constructor(private accountsService: AccountsService) {}
    private readonly logger = new Logger('Accounts Controller');

    @Post('/')
    async createAccount(
      @Body() body: CreateAccountData,
    ) {
        this.logger.debug('Create account request received')
        this.logger.debug(`Name: ${body.name}`)

      return await this.accountsService.createAccount(body.name);
    }

}

以及抛出错误的控制器的测试:

import { getModelToken, MongooseModule } from '@nestjs/mongoose';
import { Test, TestingModule } from '@nestjs/testing';
import { Account, AccountSchema } from '../entities/account.entity';
import { AccountsController } from '../accounts.controller';
import { AccountsService } from '../accounts.service';
import { CreateAccountData } from '../dto/create-account-data';
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);
    }

    jest.mock('newrelic', () => {
        return {
            recordMetric: jest.fn(),
            recordCustomEvent: jest.fn(),
        };
    });

    beforeEach(async () => {
        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));
            const nr = jest.spyOn(newrelic, 'recordCustomEvent').mockReturnValue(null);

            const u = await controller.createAccount(postBody);
            expect(newrelic.recordCustomEvent).toHaveBeenCalledWith('hello', 1);

            expect(method).toHaveBeenCalledWith(postBody.name);
            expect(u).toEqual(fakeAccount);
        });
    });
});

我的理解是,通过使用代码,下面,我会嘲笑Newrelic服务:

  jest.mock('newrelic', () => {
        return {
            recordMetric: jest.fn(),
            recordCustomEvent: jest.fn(),
        };
    });

我的问题是:

  • 嘲笑新进口的正确方法是什么?
  • 为什么NewRelic Import不找到其配置文件?

I just installed newrelic on my NestJS app and it is working fine. But now it's throwing exceptions while executing tests:

console.error
Error: New Relic requires that you name this application!
Set app_name in your newrelic.js file or set environment variable
NEW_RELIC_APP_NAME. Not starting!

Seems like, while under the test, the newrelic module cannot find the configuration located under /src/newrelic.ts.

New Relic itself is working in the app. It's just breaking the tests.

Here is my newrelic.ts file under /src:

'use strict'
/**
 * New Relic agent configuration.
 *
 * See lib/config/default.js in the agent distribution for a more complete
 * description of configuration variables and their potential values.
 */
exports.config = {
  /**
   * Array of application names.
   */
  app_name: ['product-metrics-dev'],
  /**
   * Your New Relic license key.
   */
  license_key: 'redacted',
  logging: {
    /**
     * Level at which to log. 'trace' is most useful to New Relic when diagnosing
     * issues with the agent, 'info' and higher will impose the least overhead on
     * production applications.
     */
    level: 'info'
  },
  /**
   * When true, all request headers except for those listed in attributes.exclude
   * will be captured for all traces, unless otherwise specified in a destination's
   * attributes include/exclude lists.
   */
  allow_all_headers: true,
  application_logging: {
    forwarding: {
      /**
       * Toggles whether the agent gathers log records for sending to New Relic.
       */
      enabled: true
    }
  },
  attributes: {
    /**
     * Prefix of attributes to exclude from all destinations. Allows * as wildcard
     * at end.
     *
     * NOTE: If excluding headers, they must be in camelCase form to be filtered.
     *
     * @env NEW_RELIC_ATTRIBUTES_EXCLUDE
     */
    exclude: [
      'request.headers.cookie',
      'request.headers.authorization',
      'request.headers.proxyAuthorization',
      'request.headers.setCookie*',
      'request.headers.x*',
      'response.headers.cookie',
      'response.headers.authorization',
      'response.headers.proxyAuthorization',
      'response.headers.setCookie*',
      'response.headers.x*'
    ]
  }
}

Here is a simple service that's running some NewRelic code:

import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Account } from './entities/account.entity';
import { v4 as uuidv4 } from 'uuid';
import { ObjectID } from 'bson';
import * as newrelic from 'newrelic';

@Injectable()
export class AccountsService {
    private readonly logger = new Logger('Accounts Service');
    constructor(@InjectModel(Account.name) private readonly accountModel: Model<Account>) {}

    async createAccount(name): Promise<Account> {
        //checks if name already exists
        const acc: Account = await this.accountModel.findOne({ name });

        if (acc) {
            throw new HttpException('Account name already exists', HttpStatus.BAD_REQUEST);
        }

        const now = new Date();
        const id = new ObjectID();
        const account = await this.accountModel.create({ _id: id, name, createdAt: now });
        newrelic.recordMetric('accounts_created', 1);
        newrelic.recordCustomEvent('accountCreated', { accountId: id, groupName: account.name });

        return account;
    }

    async getById(accountId: string): Promise<Account> {
        return await this.accountModel.findOne({ _id: accountId });
    }
}

And the controller that calls it:

import { Body, Controller, Logger, Post } from '@nestjs/common';
import { AccountsService } from './accounts.service';
import { CreateAccountData } from './dto/create-account-data';

@Controller('accounts')
export class AccountsController {
    constructor(private accountsService: AccountsService) {}
    private readonly logger = new Logger('Accounts Controller');

    @Post('/')
    async createAccount(
      @Body() body: CreateAccountData,
    ) {
        this.logger.debug('Create account request received')
        this.logger.debug(`Name: ${body.name}`)

      return await this.accountsService.createAccount(body.name);
    }

}

And the test for the controller that is throwing the error:

import { getModelToken, MongooseModule } from '@nestjs/mongoose';
import { Test, TestingModule } from '@nestjs/testing';
import { Account, AccountSchema } from '../entities/account.entity';
import { AccountsController } from '../accounts.controller';
import { AccountsService } from '../accounts.service';
import { CreateAccountData } from '../dto/create-account-data';
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);
    }

    jest.mock('newrelic', () => {
        return {
            recordMetric: jest.fn(),
            recordCustomEvent: jest.fn(),
        };
    });

    beforeEach(async () => {
        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));
            const nr = jest.spyOn(newrelic, 'recordCustomEvent').mockReturnValue(null);

            const u = await controller.createAccount(postBody);
            expect(newrelic.recordCustomEvent).toHaveBeenCalledWith('hello', 1);

            expect(method).toHaveBeenCalledWith(postBody.name);
            expect(u).toEqual(fakeAccount);
        });
    });
});

My understanding was that, by using the code below, I'd be mocking the newrelic service:

  jest.mock('newrelic', () => {
        return {
            recordMetric: jest.fn(),
            recordCustomEvent: jest.fn(),
        };
    });

My questions are:

  • What's the right way to mock the newrelic import?
  • Why isn't the newrelic import finding its config file?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

薄荷梦 2025-02-13 03:26:11

在新的遗物优先层次结构中,环境变量覆盖了配置文件中的内容。当我尝试此操作时,我必须从数组字符串外部更改应用程序名称,并且可以更改。

以下内容:

app_name: ['product-metrics-dev'],

因此,您还可以完全删除应用程序字符串,而不是

app_name: 'product-metrics-dev',

让环境变量 new_relic_app_name 确定应用程序的名称,与过程配对时,这可能特别有用。 Env对于单独的资源进行单个构建人工制品之类的分期和生产工作流程。

In the new relic precedence hierarchy, environment variables overwrite what is in your config file. When I tried this I had to change the app name from outside of the array string and it worked.

So instead of:

app_name: ['product-metrics-dev'],

it would be

app_name: 'product-metrics-dev',

For what its worth, you can also remove the app string entirely and let the environment variable NEW_RELIC_APP_NAME decide the name of your application, this can be particularly useful when paired with process.env for things like staging and production workflows with separate resources for a single build artefact.

一场春暖 2025-02-13 03:26:11

我能够通过开玩笑的​​配置将新的遗物配置变量作为环境变量进行解决问题。

I was able to work-around the problem by passing the new relic configuration variables as environment variables through jest configuration.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文