Testing asynchronous code
Japa has first class support for testing asynchronous workflows including Promises, events, callbacks and streams.
In this guide, we will learn how to test asynchronous code and explore various techniques to convert common async workflows to promises.
Async await
Writing tests that use promises or async/await is the recommended approach. As soon as the test callback is finished, Japa will mark the test as passed. Likewise, the test will be marked as failed in case of an error.
test('verify email address', async () => {
// await promise
await validateEmail(email)
})
test('verify email address', async () => {
// Return promise
return validateEmail(email)
})
done
method call
Waiting for If your test code relies on the event emitter, callbacks, or timeouts, then you can instruct Japa to wait for an explicit call to the done
method.
test('make redis connection', async (ctx, done) => {
const redis = new Redis()
redis.on('connected', () => {
//👇 Now the test will be marked as completed
done()
})
await redis.connect()
})
.waitForDone() // 👈 telling japa to wait for done call
In case of errors, you can pass the error to the done
method, and the test will be marked as failed.
test('make redis connection', async (ctx, done) => {
const redis = new Redis()
redis.on('connected', () => {
// passed
done()
})
redis.on('error', (error) => {
// failed
done(error)
})
await redis.connect()
})
.waitForDone()
Strategies to promisify your codebase
Even though you can write tests that rely on callbacks or events, we highly recommend you prefer async/await
over any other async API. The async/await
code reads linearly and is easy to reason.
Let us share some of the ways you can use to convert callbacks, events, or timeouts to promises.
Callbacks to promises
Node.js ships with a utility function promisify
that you can use to convert callbacks into promises.
Let's say you are testing the following piece of code.
test('generate pdf', (ctx, done) => {
pdf
.create(html, options)
.toFile('./businesscard.pdf', function(error, res) {
if (error) {
done(error)
} else {
done()
}
})
})
.waitForDone()
You can convert the above method to a promise as follows.
import { promisify } from 'util'
test('generate pdf', async (ctx) => {
const toFile = promisify(pdf.create(html, options).toFile)
await toFile()
})
const { promisify } = require('util')
test('generate pdf', async (ctx) => {
const toFile = promisify(pdf.create(html, options).toFile)
await toFile()
})
Promisify setTimeout
Most of the time, you use setTimeout
to sleep between operations. Therefore, you can create a dedicated sleep method that resolves the promise after the timeout.
const sleep = (time) => {
return new Promise((resolve) => setTimeout(resolve, time))
}
You can make use of it as follows.
test('generate pdf', async (ctx) => {
await operation1()
await sleep(5000) // for 5 seconds
await operation2()
})
Promisify event listeners
You can also convert event listeners to promises using the p-event package.
In the following example, the promise will reject if an error
event is emitted and resolve when a connected
event is emitted.
import pEvent from 'p-event'
test('make redis connection', async (ctx) => {
const redis = new Redis()
await Promise.all([
pEvent(
redis,
'connected',
{ rejectionEvents: ['error'] }
),
redis.connect()
])
})
const pEvent = require('p-event')
test('make redis connection', async (ctx) => {
const redis = new Redis()
await Promise.all([
pEvent(
redis,
'connected',
{ rejectionEvents: ['error'] }
),
redis.connect()
])
})
Promisify streams
You can promisify streams using the get-stream npm package.
import getStream from 'get-stream'
test('handle file uploads', async (ctx) => {
const buffer = await getStream(someReadableStream)
})
const getStream = require('get-stream')
test('handle file uploads', async (ctx) => {
const buffer = await getStream(someReadableStream)
})