Search code examples
node.jsjestjstwilionestjsnock

Mock Injected Twilio Service in Unit Testing Nest.js


I have a problem with overriding provider/setup module testing in nest.js application for testing.

Module file: 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, config files:

//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 service file: 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');
            });
    }

I would like to test my service: test file:

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();
    });

And after running test I get following errors:

  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 */ ]
      })

How can I solve this problem ?


Solution

  • smsTwilioConfig is registered with Nest's IOC via SmsModule.register(opts).

    However, it seems you're attempting to test TwilioService directly with createTestingModule. Which is fine, but it does mean you need include the config with a provider or import in your test. My guess is that you thought NestConfigModule... would do that, but that's not setting the config at the right level.

    I would think the following is the right direction

        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();