Testing Routes
We can test server routes using Jest, Supertest, and Testing Library.
Setting up
We do this first and foremost by exporting the Express server object from server.js (or wherever it happens to be defined):
// server.jsconst server = express()server.get('/example', (req, res) => {res.send('WOMBAT')})module.exports = server
Importantly, we want to put the server.listen() call in another file (index.js, for example). This allows our testing code to require('./server.js'). If we put the .listen() call in server.js, it will execute when the file is read and we don't actually want the server listening for connections while we're running our tests. This implies that we can tell things about our application without running it at all! Handy.
index.js can be pretty simple:
// index.jsconst server = require('./server')const port = process.env.PORT || 3000server.listen(port, function () {console.log('Server listening on port:', port)})
Writing route tests
By convention, Supertest is usually called from the variable name request.
const request = require('supertest')const server = require('../server.js')test('/example returns WOMBAT', done => {// Arrangeconst expected = 'WOMBAT'// Actreturn request(server).get('/example').then((err, res) => {// Assertexpect(res.status).toBe(200)expect(res.text).toBe(expected)done()})})
Notice that we put our assertion inside a callback (the anonymous function passed to .then). Supertest will give us the response from the server here, or any error messages. Supertest calls it for us when it's done requesting the /example route from our server.
The text/html content sent back in the response can be found in the res.text property.
In pseudocode, we might write:
SEND a request to the /example routeWAIT for a responseWHEN the response comes,COMPARE it with what we expected it to be
We can also check what the HTTP status code was that the server sent back (for example, 200 for OK or 404 for NOT FOUND):
return request(app).get('/example').expect(200)
HTML
If we want to test what users will experience in the HTML, we can use testing-library to query for features we expect to be present in the HTML. Testing library provides queries and assertions that are about the parts of the UI that the users will actually experience, e.g. the text in an element or the label of a form input.
This makes our tests less brittle and more realistic, because we tend to test things that will affect users.
To run these queries, we first need to load the HTML in a simulated DOM environment. We can create a reusable function to help us:
// test-utils.jsconst { within } = require('@testing-library/dom')const { JSDOM } = require('jsdom')const render = (response) => {const { document } = new JSDOM(response.text).windowreturn within(document)}module.exports = render
We can then use this within our route test:
// server.test.jstest('contains the word "Welcome"', () => {return request(server).get('/').then((response) => {const screen = render(response)const welcome = screen.getByText(/Welcome/)expect(welcome).toBeInTheDocument()expect(welcome).toBeVisible()})})
Resources
- Testing library intro and guiding principles
- Testing Library queries