I'm trying to write a test in nodeJS using Jest for a function that calls two async functions one after the other. I want to delay funcA, expect funcB to not be called, then run the timer down and expect funcB to be called.
The code looks something like this
//module1.js
async function mainFunc() {
await module2.funcA()
await module2.funcB()
}
//module2.js
async function funcA(){
// making some async operation
}
async function funcB(){
// making some async operation
}
I've tryed mocking the implementation of funcA like this:
const spyOnFuncA = jest.spyOn(module2, 'funcA').mockImplementation(async () => new Promise((r) => setTimeout(r, 1000)))
then in the test doing something like this:
test('Should not call second function until first function resolved', async () => {
jest.useFakeTimers()
const spyOnFuncA = jest.spyOn(module2, 'funcA').mockImplementation(async () => new Promise((r) => setTimeout(r, 1000)))
const spyOnFuncB = jest.spyOn(module2, 'funcB').mockImplementation()
mainFunc()
expect(spyOnFuncA).toBeCalled()
expect(spyOnFuncB).not.toBeCalled()
jest.runAllTimers()
expect(spyOnFuncB).toBeCalled()
})
I think the problem here is that the jest.useFakeTimers
contradicts with the setTimeout inside the mockImplementation
Any ideas how should I test this?
Would appreciate any idea
Cheers!
CodePudding user response:
Mock funcA
to return a deferred promise, to be resolved later. I know sinon provides a promise helper to cover deferred so jest may include a similar construct. Otherwise here is one of the simple implementations from that answer:
class Deferred {
constructor() {
this.promise = new Promise((resolve, reject) => {
this.reject = reject
this.resolve = resolve
})
}
}
Then the mock is something like:
const deferred = new Deferred()
const spyOnFuncA = jest.spyOn(module2, 'funcA').mockImplementation(() => deferred.promise)
mainFunc() // avoid uncaught exceptions with `.catch`
.catch(err => expect(err).toBe(null))
expect(spyOnFuncA).toBeCalled()
expect(spyOnFuncB).not.toBeCalled()
deferred.resolve('whatever')
expect(spyOnFuncB).toBeCalled()
CodePudding user response:
The fake timer approach is correct, but you need to wait for the promise returned from mainFunc
completed.
module1.js
:
import * as module2 from './module2';
export async function mainFunc() {
await module2.funcA();
await module2.funcB();
}
module2.js
:
export async function funcA() {
// making some async operation
}
export async function funcB() {
// making some async operation
}
module1.test.js
:
import { mainFunc } from './module1';
import * as module2 from './module2';
describe('73707110', () => {
test('Should not call second function until first function resolved', async () => {
jest.useFakeTimers();
const spyOnFuncA = jest.spyOn(module2, 'funcA').mockImplementation(() => new Promise((r) => setTimeout(r, 1000)));
const spyOnFuncB = jest.spyOn(module2, 'funcB').mockImplementation();
const promise = mainFunc();
expect(spyOnFuncA).toBeCalled();
expect(spyOnFuncB).not.toBeCalled();
jest.runAllTimers();
await promise;
expect(spyOnFuncB).toBeCalled();
});
});
Test result:
PASS stackoverflow/73707110/module1.test.ts (10.534 s)
73707110
✓ Should not call second function until first function resolved (4 ms)
------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 33.33 | 100 |
module1.ts | 100 | 100 | 100 | 100 |
module2.ts | 100 | 100 | 0 | 100 |
------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 11.156 s
"jest": "^26.6.3"