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 catch
block 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
the ServiceBusClient import imports the mock version rather than the real one
The mock version's
createSender
method returns a mock sender.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:
- Jest Manual Mocks
- Customisable manual mocks in Jest and Typescript
- Using Jest Mocks TypeScript the Right Way
- Mocking TypeScript classes with Jest
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')
})
})