Home > Back-end >  How to I simulate a ServiceBusError exception for Jest testing?
How to I simulate a ServiceBusError exception for Jest testing?

Time:01-16

I have an azure function like so (not the actual function, just a representation is given below)

import { AzureFunction, Context, HttpRequest } from '@azure/functions'
import { ServiceBusClient, isServiceBusError } from '@azure/service-bus'

const serviceBusClient = new ServiceBusClient(clientConnectionString)
const sender = serviceBusClient.createSender(topicName)

const func: AzureFunction = async function (
  context: Context,
  req: HttpRequest
): Promise<void> { 
try {
sender.sendMessages({ body: {a:"a",b:"b",c:"c" })
}
catch (error: unknown) {
if (isServiceBusError(error)) {
      message = `This is the error message ${error.message} with the reason ${error.code}`
      context.log.error(`Error Message: ${message}`)
    } else {
      context.log.error(`Error Message: Error Encountered`)
    }
}
}

How do to test if the code in the catchblock is working properly? Specifically, how do I throw a ServiceBusError to test that it is caught and appropriate error message is logged?

Can any one guide or provide hint on how to proceed?

Update

I already have the following code to mock the service bus & also to check is an error is a Service bus error. However, I am not able to understand how I can have this function to mock both a 'good' case (message sent successfully) and a 'bad' case (where ServiceBusError is thrown`).

jest.mock('@azure/service-bus', () => {
  return {
    isServiceBusError: jest.fn().mockImplementation((err: unknown) => {
      if (err === ServiceBusError) {
        return true
      } else return false
    }),
    ServiceBusClient: jest.fn().mockImplementation(() => {
      return {
        createSender: jest.fn().mockImplementation(() => {
          return {
            sendMessages: mockSendMessages,
          }
        }),
      }
    }),
  }
})

I test is like so

test('Bad case: Empty Data', async () => {
  const emptyData = {}

  const wrongRequest = {
    query: {},
    body: emptyData,
  }

  // Action
  await func(context, wrongRequest)

  expect(mockSendMessages).toHaveBeenCalledTimes(0)
})

How to use the mock to throw the error is where I am getting confused (fyi, the above test case works just fine).

CodePudding user response:

Use mock testing

You need to replace the sender.sendMessages function with a mock version that you can control, e.g. throw an exception in your test case.

The technique of using mock versions of services, objects or functions that you can control in your test code to simulate arbitrary conditions, states or errors is a well established best practice. You can find a ton of information on the internet about mock testing.

General info about mocking:

Use Jest Manual Mocks to create a mock ServiceBusClient

In essence, you will have to use Jest manual mocks so that

  1. the ServiceBusClient import imports the mock version rather than the real one

  2. The mock version's createSender method returns a mock sender.

  3. The mock sender's sendMessages method does whatever you want it to in your test (e.g. throw a specific exception). You do that by configuring the mock before your test calls the code being tested.

Here are some Jest- and Typescript-specific resources that can help you:

CodePudding user response:

Here's an example of a Jest unit test source matching your needs (I hope):

import type { AzureFunction, Context } from '@azure/functions'
import type { ServiceBusSender } from '@azure/service-bus'
import { when } from 'jest-when'

describe('service bus client unit tests', () => {
  const clientConnectionString = 'myConnectionString'
  const topicName = 'myTopic'
  let ServiceBusClient: jest.SpyInstance
  let isServiceBusError: jest.SpyInstance
  let serviceBusClient: { createSender: (topicName: string) => ServiceBusSender }
  let sender: jest.Mocked<ServiceBusSender>
  let func: AzureFunction
  beforeAll(() => {
    ServiceBusClient = jest.fn()
    isServiceBusError = jest.fn()
    serviceBusClient = { createSender: jest.fn() }
    sender = { sendMessages: jest.fn() } as unknown as jest.Mocked<ServiceBusSender>
    jest.doMock('@azure/service-bus', () => ({ ServiceBusClient, isServiceBusError }))
    when(ServiceBusClient).calledWith(clientConnectionString).mockReturnValue(serviceBusClient)
    when(serviceBusClient.createSender).calledWith(topicName).mockReturnValue(sender)
    func = require('./sample').func
    expect(ServiceBusClient).toHaveBeenNthCalledWith(1, clientConnectionString)
    expect(serviceBusClient.createSender).toHaveBeenNthCalledWith(1, topicName)
  })
  afterEach(() => {
    jest.resetAllMocks()
  })
  afterAll(() => {
    jest.restoreAllMocks()
  })
  test('should log error given a service bus error', async () => {
    // Given
    const contextLogError = jest.fn()
    const context = { log: { error: contextLogError } } as unknown as Context
    const messageBody = { body: { a: 'a', b: 'b', c: 'c' } }
    const error: Error & { code?: string } = new Error('oops')
    error.code = 'MyCode'
    when(sender.sendMessages).calledWith(messageBody).mockRejectedValue(error)
    when(isServiceBusError).calledWith(error).mockReturnValue(true)
    // When
    await func(context)
    // Then
    expect(sender.sendMessages).toHaveBeenNthCalledWith(1, messageBody)
    expect(isServiceBusError).toHaveBeenNthCalledWith(1, error)
    expect(contextLogError).toHaveBeenNthCalledWith(
      1,
      'Error Message: This is the error message oops with the reason MyCode',
    )
  })
  test('should log error given any other error', async () => {
    // Given
    const contextLogError = jest.fn()
    const context = { log: { error: contextLogError } } as unknown as Context
    const messageBody = { body: { a: 'a', b: 'b', c: 'c' } }
    const error = new Error('oops')
    when(sender.sendMessages).calledWith(messageBody).mockRejectedValue(error)
    when(isServiceBusError).calledWith(error).mockReturnValue(false)
    // When
    await func(context)
    // Then
    expect(sender.sendMessages).toHaveBeenNthCalledWith(1, messageBody)
    expect(isServiceBusError).toHaveBeenNthCalledWith(1, error)
    expect(contextLogError).toHaveBeenNthCalledWith(1, 'Error Message: Error Encountered')
  })
})
  • Related