0

In order to automate testing of my Express.js, I want to make sure that the tests have finished running when each test decides the results.

Consider this simplified model of that situation:

main.js

#!/usr/bin/env node
const express = require('express')
const app = express()
app.get('/', (req, res) => {
  res.send('hello world')
})
const server = app.listen(3000, function () {
  // I would run tests here, which is the 'listen' event.
  console.log('Server started')
  // After the tests, I will close the server from here.
  this.close()
})
// I want to make sure that this runs only after the asserts were done,
// otherwise the test would miss possible failures.
console.log('After listen')

package.json

{
  "name": "tmp",
  "version": "1.0.0",
  "dependencies": {
    "express": "4.17.1"
  }
}

Running main produces:

After listen
Server started

because the server must be asynchronously starting to listen to connections, so After listen runs first. But I want instead to have:

Server started
After listen

I know about the 'close' event which would allow me to write:

#!/usr/bin/env node
const express = require('express')
const app = express()
app.get('/', (req, res) => {
  res.send('hello world')
})
const server = app.listen(3000, function () {
  // I would run tests here, which is the 'listen' event.
  console.log('Server started')
  // After the tests, I will close the server from here.
  this.close()
})
server.on('close', () => {
  console.log('After listen')
})

but then this just shifts the problem further out to the test infrastructure, which has to make sure that it is able to wait for the After listen to happen. Yes, in Mocha this is possible with done(), but it would be better to have something simpler more test-system agnostic.

Tested on Node.js 14.15.0.

Related:

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985

1 Answers1

0

OK, I've studied promises a bit more as I should have done earlier, and this is satisfactory approach:

#!/usr/bin/env node
(async () => {
const express = require('express')
const app = express()
app.get('/', (req, res) => {
  res.send('hello world')
})
await new Promise((resolve, reject) => {
  const server = app.listen(3000, function () {
    // I would run tests here, which is the 'listen' event.
    console.log('Server started')
    // After the tests, I will close the server from here.
    this.close()
    resolve()
  })
})
console.log('After listen')
})()

This is basically the same general pattern that you can use to synchronize any other asynchronous callback function in JavaScript, e.g. for HTTP requests: Synchronous request in Node.js

Here I illustrate a fuller test setup which also makes the HTTP requests and asserts things: What's the best way to test express.js API

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985