Reputation: 11
I've done some searching around but havent managed to find the answer to my current predicament. I have a function (addMultipleBooks
) that loops through an array and within that loop, has an async setTimeout function that awaits another function (addNewBook
).
I've been attempting to write a unit test for the main/parent function that checks whether or not the child function get's called however the test's fail stating that
Expected number of calls: >= 1
Received number of calls: 0
There is also an additional message shortly after
● Cannot log after tests are done. Did you forget to wait for something async in your test?
Attempted to log "message: undefined".
● Cannot log after tests are done. Did you forget to wait for something async in your test?
Attempted to log "percentage: 100".
● Cannot log after tests are done. Did you forget to wait for something async in your test?
Attempted to log "message: undefined".
● Cannot log after tests are done. Did you forget to wait for something async in your test?
Attempted to log "percentage: 100".
How can I ensure that the child function is being awaited within my tests? Will I need to re-write the parent function? I've attempted to use jest.useFakeTimers()
but to no avail. Any suggestions?
Thanks in advance
//--------- PARENT.TEST.TS ---------
it('should call addNewBook for each book parsed from results file', async () => {
jest.useFakeTimers();
addMultipleBooks(DUMMY_USER_ID);
jest.runAllTimers();
expect(addNewBookSpy).toHaveBeenCalled();
});
//--------- PARENT.TS ---------
function addMultipleBooks(userID: string) {
const data = fs.readFileSync('./results.json');
try {
const booksArray: formattedBook[] = JSON.parse(data.toString());
booksArray.map((book, index: number) => {
setTimeout(async () => {
const message = await addNewBook(book, userID);
console.log('message: ', message);
const percentage = Math.floor(((index + 1) / booksArray.length) * 100);
console.log('percentage: ', percentage);
}, (index + 1) * 1000);
});
} catch (error) {
console.log('error parsing book: ', error);
}
}
Upvotes: 0
Views: 951
Reputation: 3331
You need to await the operations you are doing.
Currently the check happens before the operations are complete and then jest notices stuff still running afterwards and complains.
If you want to originate some asynchronous behaviour (which is not already Promise oriented - like your setTimeout which is based on a callback API) then you need to learn about how to create Promises with new Promise.
Once you have an array of promises you can await Promise.all(arrayOfPromises). The line after your await will then occur only after everything has completed
I put together a demonstration which closes some of the gaps in your code at https://tsplay.dev/N5Pydw and shown below. When you run it in the playground you can see that things happen in the right order.
async function addNewBook(options:{book:string, userId:string}){
const {book, userId} = options;
console.log(book, userId)
}
async function addMultipleBooks(books:ReadonlyArray<string>, userId: string) {
try {
await Promise.all(books.map(async (book, index: number) => {
await new Promise((resolve) => setTimeout(resolve, (index + 1) * 1000));
const message = await addNewBook({book, userId});
console.log('message: ', message);
const percentage = Math.floor(((index + 1) / books.length) * 100);
console.log('percentage: ', percentage);
}))
} catch (error) {
console.log('error parsing book: ', error);
}
}
async function test(){
const testBooks = ["book1", "book2"] as const;
const testUserId = "spiderman";
await addMultipleBooks(testBooks, testUserId)
console.log("Assertions here")
}
test()
Upvotes: 1