Rohit Vishwakarma
Rohit Vishwakarma

Reputation: 530

How to unit test twilio for programmable Video API in typescript?

I'm using twilio package in my backend which uses loopback4 with Typescript. I'm writing unit test cases using Mocha,Jest and sinon but facing following difficulties.

I'm having a twilio service which looks like this

export class TwilioService {
  twilioBaseUrl = 'https://video.twilio.com';
  twilioClient: TwilioClient;
  constructor(
    @inject(TwilioBindings.config) 
    private readonly twilioConfig: TwilioConfig,
    @repository(VideoChatSessionRepository)
    private readonly videoChatSessionRepository: VideoChatSessionRepository,
    @repository(SessionAttendeesRepository)
    private readonly sessionAttendeesRepository: SessionAttendeesRepository,
  ) {
    const {accountSid, apiSid, apiSecret, authToken} = twilioConfig;
    if (!(accountSid && apiSid && apiSecret && authToken)) {
      throw new HttpErrors.BadRequest(`Twilio API credentials are not set`);
    }
    this.twilioClient = twilio(twilioConfig.accountSid, twilioConfig.authToken);
  }

   async deleteArchive(archiveId: string): Promise<void> {
    const result = await this.twilioClient.video
      .recordings(archiveId)
      .remove((err, items) => {
        if (err) {
          throw new HttpErrors.ExpectationFailed(`Error deleting archive`);
        }
      });
    if (!result) {
      throw new HttpErrors.ExpectationFailed(`Error deleting archives`);
    }

    return Promise.resolve();
  }
}

Now here for the method deleteArchive(arciveId:string):Promise<void>

I write the test cases as

  describe('deleteArchive', () => {
    it('delete the archive with given arhive id', async () => {
      const recordingContext = sinon.createStubInstance(RecordingContext);
      recordingContext.remove.resolves(true);
      sinon
        .stub(twilioService.twilioClient.video, 'recordings')
        .returns(recordingContext);

      const result = await twilioService.twilioClient.video
        .recordings(archiveId)
        .remove();
      sinon.assert.calledOnce(recordingContext.remove);
      
    });
  });

it gives me error that

 Error: The requested resource /Recordings/RTXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX was not 
  found
  at success (node_modules/twilio/lib/base/Version.js:113:15)
  at Promise_then_fulfilled (node_modules/q/q.js:766:44)
  at Promise_done_fulfilled (node_modules/q/q.js:835:31)
  at Fulfilled_dispatch [as dispatch] (node_modules/q/q.js:1229:9)
  at Pending_become_eachMessage_task (node_modules/q/q.js:1369:30)
  at RawTask.call (node_modules/asap/asap.js:40:19)
  at flush (node_modules/asap/raw.js:50:29)
  at processTicksAndRejections (node:internal/process/task_queues:78:11)

The actual issue that seems to me is that actual recordings method is called instead of my stubbed one, which throws the error.

So my question is twilioClient.video.recordings returns an object which has a method remove how to deal with such scenario where we want to stub a method of an object which is also the result of stubbed method.

Upvotes: 0

Views: 235

Answers (1)

Rohit Vishwakarma
Rohit Vishwakarma

Reputation: 530

The problem that I figured out is my stubbed function was not called. Instead it is the actual one which throws the error.

So I mocked the entire twilio library using proxyquire as follows:

  describe('deleteArchive', () => {
it('delete the archive with given arhive id', async () => {
  const removeStub = sinon.stub().resolves(true);
  const recordingStub = sinon.stub().returns({
    remove: removeStub,
  });

  const TwilioMockService = proxyquire(
    '../../../providers/twilio/twilio.service',
    {
      twilio: (accountSid: string, authToken: string) => {
        return {
          video: {
            recordings: recordingStub,
          },
        };
      },
    },
  ).TwilioService;
  const twilioMockServiceInstance = new TwilioMockService(
    twilioConfig,
    videoChatSessionRepo,
    sessionAttendeesRepo,
  );
  twilioProvider = new TwilioProvider(twilioMockServiceInstance);

  await twilioProvider.value().deleteArchive(archiveId);
  sinon.assert.calledOnce(removeStub);
});
});

PS: twilio provider is just a wrapper over TwilioService. It just exposes the methods of twilioServie and nothing else.

It is required because of the lb4 architecture and nothing else

Upvotes: 1

Related Questions