Fundamentals of Testing in TypeScript #4
November 27, 2021Open playground
In the previous article, we have created a simple testing framework for TypeScript. It works perfectly for synchronous tests, but what if we want to test some asynchronous code? We can use the async
keyword to make our callback asynchronous, and then we can use the await
keyword to wait for the callback to resolve. After that, we can make our test assertion.
Making our add and subtract functions asynchronous
To make our add and subtract functions asynchronous, we need to make them return a promise. For the sake of simplicity, we will use Promise.resolve()
to return a resolved promise.
export const add = (a: number, b: number): number => {return a - b}export const subtract = (a: number, b: number): number => {return a - b}export const addAsync = (a: number, b: number) => {return Promise.resolve(add(a, b))}export const subtractAsync = (a: number, b: number) => {return Promise.resolve(subtract(a, b))}
Now, we need to update our test
callback function to be an async function, and then we will await addAsync
respectively subtractAsync
import { addAsync, subtractAsync } from './'const expect = <T>(value: T) => {return {toBe(expected: T) {if (value !== expected) {throw new Error(`${value} is not equal to ${expected}`)}},}}const test = <T>(title: string, callback: () => T) => {try {callback()console.log(`✅ ${title}`)} catch (error) {console.error(`❌ ${title}`)console.error(error)}}test('addAsync function', async () => {const result = await addAsync(8, 16)expect(result).toBe(24)})test('subtractAsync function', async () => {const result = await subtractAsync(32, 16)expect(result).toBe(16)})
If we look into our console, we could see that our tests are passing, even tho we know that we have a bug in our code. When you run the test locally, you could see a different error message, e.g. UnhandledPromiseRejectionWarning
, which is the error coming from the addAsync
function.
Making our test function asynchronous
To fix this error, we need to make our test function asynchronous. We can do this by adding the async
keyword to the test function, and then we can await
the callback.
import { addAsync, subtractAsync } from './'const expect = <T>(value: T) => {return {toBe(expected: T) {if (value !== expected) {throw new Error(`${value} is not equal to ${expected}`)}},}}const test = async <T>(title: string, callback: () => T) => {try {// `callback` is now an async function and returns a promise// if promise is rejected, we will jump into the `catch` block// if promise is resolved, we will continue evaluating the `console.log`await callback()console.log(`✅ ${title}`)} catch (error) {console.error(`❌ ${title}`)console.error(error)}}test('addAsync function', async () => {const result = await addAsync(8, 16)expect(result).toBe(24)})test('subtractAsync function', async () => {const result = await subtractAsync(32, 16)expect(result).toBe(16)})
If we look into our console, we could see that our tests are addAsync
function is no longer passing.
Our async test
function is now ready for both synchronous and asynchronous tests.
Conclusion
At first, testing the async function can be a bit tricky. But after a while, it becomes easier as you learn more about asynchronous code.
Did you encounter any problems? Let me know, and we can try to squash the bugs!