I'm testing that a (constructor) function is called with a particular object, however the object itself has properties which themselves are JSON. This is because the third-party API which I am mocking expects this type of object, so I can't change it.
I want to test these JSON properties by converting them back to objects and using something like expect.objectContaining, because it is less brittle than trying to match the complete object, or worse still the JSON string.
I am testing an AWS Lambda SNS send function but I don't think that's entirely relevant, except to explain the syntax of my system under test:
export async function handler(event: any) {
let sns = new SNSClient({ region: REGION });
let myMessage = {}; // create my message
let params = {
Message: JSON.stringify(myMessage),
TopicArn: 'my ARN'
}
const result = await sns.send(new PublishCommand(params));
// some stuff
return true;
}
My tests:
import { handler } from "./handler";
import { SNSClient, PublishCommand } from "@aws-sdk/client-sns";
const mockSnsSend = jest.fn();
mockSnsSend.mockResolvedValue({ MessageId: 1 });
jest.mock('@aws-sdk/client-sns', () => {
return {
SNSClient: jest.fn().mockImplementation(() => {
return {
send: mockSnsSend
}
}),
PublishCommand: jest.fn().mockImplementation()
}
});
beforeEach(() => {
// Clear all instances and calls to constructor and all methods:
mockSnsSend.mockClear();
(SNSClient as any).mockClear();
(PublishCommand as any).mockClear();
expect.hasAssertions();
});
test('Calls PublishCommand with correct message payload', async () => {
let results = await handler(sampleLoraThingMessage);
expect(PublishCommand).toHaveBeenCalledTimes(1);
// what to do here?
});
What I've tried:
expect a string
This is easy and passes, but it is not sufficient:
expect(PublishCommand).toHaveBeenCalledWith(expect.objectContaining({
Message: expect.any(String)
}));
extend expect
As per the docs I've tried extending expect to parse the JSON and then run an equals test over it, but I found that this is only available to the expect(a).hasJsonMatchingObject(b) syntax, and I can't use it with toHaveBeenCalledWith.
expect.extend({
hasJsonMatchingObject(received, propertyName: string, actual: object) {
let parsed = JSON.parse(received[propertyName]);
let pass = this.equals(parsed, actual, undefined, false);
return {
pass: pass,
// todo: I haven't handled the .not() case here yet
message: () => `property ${propertyName} must have JSON with certain properties:` +
'\n\n' +
`Expected: not ${this.utils.printExpected(actual)}\n` +
`Received: ${this.utils.printReceived(received)}`
};
}
})
...
expect(PublishCommand).toHaveBeenCalledWith(expect.hasJsonMatchingObject(...)); // expect has no property 'hasJsonMatchingObject'