Fundamentals of Testing in TypeScript #4

November 27, 2021

Open 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.

index.ts
1export const add = (a: number, b: number): number => {
2 return a - b
3}
4
5export const subtract = (a: number, b: number): number => {
6 return a - b
7}
8
9export const addAsync = (a: number, b: number) => {
10 return Promise.resolve(add(a, b))
11}
12
13export const subtractAsync = (a: number, b: number) => {
14 return Promise.resolve(subtract(a, b))
15}

Now, we need to update our test callback function to be an async function, and then we will await addAsync respectively subtractAsync

index.tests.ts
1import { addAsync, subtractAsync } from './'
2
3const expect = <T>(value: T) => {
4 return {
5 toBe(expected: T) {
6 if (value !== expected) {
7 throw new Error(`${value} is not equal to ${expected}`)
8 }
9 },
10 }
11}
12
13const test = <T>(title: string, callback: () => T) => {
14 try {
15 callback()
16 console.log(`${title}`)
17 } catch (error) {
18 console.error(`${title}`)
19 console.error(error)
20 }
21}
22
23test('addAsync function', async () => {
24 const result = await addAsync(8, 16)
25 expect(result).toBe(24)
26})
27
28test('subtractAsync function', async () => {
29 const result = await subtractAsync(32, 16)
30 expect(result).toBe(16)
31})

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.

index.tests.ts
1import { addAsync, subtractAsync } from './'
2
3const expect = <T>(value: T) => {
4 return {
5 toBe(expected: T) {
6 if (value !== expected) {
7 throw new Error(`${value} is not equal to ${expected}`)
8 }
9 },
10 }
11}
12
13const test = async <T>(title: string, callback: () => T) => {
14 try {
15 // `callback` is now an async function and returns a promise
16 // if promise is rejected, we will jump into the `catch` block
17 // if promise is resolved, we will continue evaluating the `console.log`
18 await callback()
19 console.log(`${title}`)
20 } catch (error) {
21 console.error(`${title}`)
22 console.error(error)
23 }
24}
25
26test('addAsync function', async () => {
27 const result = await addAsync(8, 16)
28 expect(result).toBe(24)
29})
30
31test('subtractAsync function', async () => {
32 const result = await subtractAsync(32, 16)
33 expect(result).toBe(16)
34})

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!

Share this post on Twitter