Given a function with a signature
interface Position {
x: number, y: number
}
function f(position: Position): void { … }
I want to mock it in a test
// given
const f = jest.fn()
// when
// exercise SUT
// then
expect(f).toHaveBeenCalledWith<Position>({x: 1.0, y: 1.0});
But unfortunately, I'm dealing with floating points, so I need the check to allow for some leeway. Now, I could do
expect(f.mock.calls[0][0].x).toBeCloseTo(1, 1);
expect(f.mock.calls[0][0].y).toBeCloseTo(1, 1);
But that gets old real fast. There's a ticket that the Jest maintainers don't seem to think warrants inclusion in Jest and a package to match using expect but it doesn't do what I'd like to do! Which something like:
expect(f).toHaveBeenCalledWith(
expect.objectContaining({
x: expect.numberCloseTo(1),
y: expect.numberCloseTo(1)
});
Is there something like that or do I have to roll my own?
As Aleksey already hinted at, I needed to write my own custom matcher. So, here it goes:
declare global {
namespace jest {
interface Expect {
numberCloseTo(expected: number, precision?: number): any;
}
}
}
expect.extend({
numberCloseTo(actual: number, expected: number, precision: number = 2) {
expect(actual).toBeCloseTo(expected, precision);
return { message: () => "This shouldn't happen.", pass: true };
},
});
And here's how I use it:
expect(f).toHaveBeenCalledWith(
expect.objectContaining<Position>({
x: expect.numberCloseTo(1, 1),
y: expect.numberCloseTo(1, 1),
})
);
Extending already existing matchers is super-awkward in Jest, and I guess that's by design. So we need to include a dummy message.
The TS only declare global etc. can be dropped if you're using Flow or plain JS.