I wrote about fixing flaky test in June, here’s more discoveries over the last few months.
This time it’s mostly async-related.

Mock implementation of a hook that takes a callback, and resolves the callback asynchronously

My original implementation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const mockUseTransaction = jest.fn()

jest.mock('./hooks/use-transaction/useTransaction', () => ({
useTransaction: (_: any, onComplete: (transaction: any) => void) =>
mockUseTransaction(onComplete),
}))

test('reverses payment if user declines the signature', async () => {
mockUseTransaction.mockImplementationOnce(
async (onComplete: (transaction: any) => void) => {
// mock the behavior of query loading
await wait(10)
onComplete(APPROVED_WITH_SIGNATURE_TRANSACTION)
}
)

If I do not await wait(10), I will get a warning:

1
Warning: Cannot update a component (`SwiperPaymentModal`) while rendering a different component (`ProcessingTransaction`). To locate the bad setState() call inside `ProcessingTransaction`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render

This is because when component is first rendered, SwiperPaymentModal starts from processing status, and tries to render ProcessingTransaction; When onComplete callback is invoked synchronously, the parent (SwiperPaymentModal) will change state via useReducer.

Adding a wait(10) is not exactly “flaky” as it should always pass, but the waiting time is not elegant.

Updated implementation

1
2
3
4
5
mockUseTransaction.mockImplementationOnce(
async (onComplete: (transaction: any) => void) => {
onComplete(await Promise.resolve(APPROVED_TRANSACTION))
}
)

I don’t really need 10ms, all I need is that onComplete is not invoked synchronously. Here we go.