在单元测试中模拟注入的 Twilio 服务 Nest.js

Mock Injected Twilio Service in Unit Testing Nest.js

我在 nest.js 测试应用程序中覆盖 provider/setup 模块测试时遇到问题。

模块文件:smsModule.ts:

import { TwilioService } from './twilio/twilio.service';
import { DynamicModule, Module } from '@nestjs/common';
import { TwilioConfig, SMS_TWILIO_CONFIG } from './twilio.config';
import { TwilioClientCustom } from './twilio/twilio-client-custom';

@Module({
    imports: [],
    providers: [TwilioService],
})
export class SmsModule {
    static register(options: TwilioConfig): DynamicModule {
        return {
            module: SmsModule,
            imports: [HttpModule],
            providers: [
                {
                    provide: SMS_TWILIO_CONFIG,
                    useValue: options,
                },
                TwilioService,
                TwilioClientCustom,
            ],
            exports: [TwilioService],
        };
    }
}

Twilio 客户端,配置文件:

//client

import { TwilioConfig, SMS_TWILIO_CONFIG } from '../twilio.config';
import { Twilio } from 'twilio';
import { Inject, Injectable } from '@nestjs/common';

@Injectable()
export class TwilioClientCustom extends Twilio {
    constructor(@Inject(SMS_TWILIO_CONFIG) twilioConfig: TwilioConfig) {
        super(twilioConfig.accountSid, twilioConfig.authToken);
    }
}


//config 

import { IsString, IsNotEmpty, NotContains, IsOptional, IsArray } from 'class-validator';

const INFO = 'Must be ....';
export class TwilioConfig {
    @IsString()
    @IsNotEmpty()
    @NotContains('OVERRIDE_WITH_', { message: INFO })
    accountSid: string;

    @IsString()
    @IsNotEmpty()
    authToken: string;

    @IsArray()
    @IsOptional()
    @IsNotEmpty('OVERRIDE_WITH_', { message: INFO })
    serviceSid: string;
}
export const SMS_TWILIO_CONFIG = 'smsTwilioConfig';


Twilio 服务文件:twilio.service.tst:

import { HttpService } from '@nestjs/axios';
import { TwilioConfig, SMS_TWILIO_CONFIG } from '../twilio.config';
import { SendSmsTwilioService } from './../sendsms.service';
import { Inject, Injectable } from '@nestjs/common';
import { TwilioClientCustom } from './twilio-client-custom';

@Injectable()
export class TwilioService implements SendSmsTwilioService {
    constructor(
        @Inject(SMS_TWILIO_CONFIG) private readonly config: TwilioConfig,
        private readonly client: TwilioClientCustom,
        private readonly httpService: HttpService
    ) {}
    async sendSMS(to: string, from: string, body: string): Promise<string> {

        ......

        return this.client.messages
            .create({
                to, //Recipinet's number
                from, //Twilio number
                body, //Messages to Recipient
            })
            .then((message) => message.sid)
            .catch(() => {
                throw new Error('TWILIO accountSid or authToken not valid');
            });
    }

我想测试我的服务: 测试文件:

import { Test, TestingModule } from '@nestjs/testing';
//import { TWILIO_CONFIG_SPEC } from './test.config';
import { TwilioClientCustom } from '../src/twilio/twilio-client-custom';
import { HttpService } from '@nestjs/axios';
import { TwilioConfig } from './../src/twilio.config';
import { TwilioService } from './../src/twilio/twilio.service';
import nock from 'nock';

describe('TwilioService', () => {
    let service: TwilioService;
    let client: TwilioClientCustom;
    let httpService: HttpService;

    afterEach(() => {
        nock.cleanAll();
    });

    //const smsServiceMock = {};

    beforeEach(async () => {
        const moduleRef: TestingModule = await Test.createTestingModule({
            providers: [
                TwilioService,

                {
                    provide: HttpService,
                    useValue: {
                        method1: jest.fn(),
                        method2: jest.fn(),
                        method3: jest.fn(),
                    },
                },
                TwilioService,
            ],
            imports: [
                NestConfigModule.forRoot({
                    config: TwilioConfig,
                } as Record<string, unknown>),
            ],
        }).compile();
        //getting service module from main module
        httpService = moduleRef.get<HttpService>(HttpService);
        client = moduleRef.get<TwilioClientCustom>(TwilioClientCustom);
        service = moduleRef.get<TwilioService>(TwilioService);
    });
    //check service is avaible
    it('Should be defined', () => {
        expect(client).toBeDefined();
        expect(service).toBeDefined();
        expect(httpService).toBeDefined();
    });

在 运行 测试后我得到以下错误:

  Nest can't resolve dependencies of the TwilioService (?, TwilioClientCustom, HttpService). Please make sure that the argument smsTwilioConfig at index [0] is available in the RootTestModule context.

    Potential solutions:
    - If smsTwilioConfig is a provider, is it part of the current RootTestModule?
    - If smsTwilioConfig is exported from a separate @Module, is that module imported within RootTestModule?
      @Module({
        imports: [ /* the Module containing smsTwilioConfig */ ]
      })

我该如何解决这个问题?

smsTwilioConfig 通过 SmsModule.register(opts).

向 Nest 的 IOC 注册

但是,您似乎正试图用 createTestingModule 直接测试 TwilioService。这很好,但这确实意味着您需要在测试中包含提供程序的配置或导入。 我的猜测是您认为 NestConfigModule... 会那样做,但那不是在正确的级别设置配置。

我认为以下是正确的方向

    const moduleRef: TestingModule = await Test.createTestingModule({
        providers: [
            TwilioService,
            {
                provide: HttpService,
                useValue: {
                    method1: jest.fn(),
                    method2: jest.fn(),
                    method3: jest.fn(),
                },
            },
            {
                // added this
                provide: SMS_TWILIO_CONFIG,
                useValue: testConfig
            },
        ],
        imports: [
            // removed NestConfigModule
        ],
    }).compile();