node.jsunit-testingjestjskoakoa2

Open handles while testing Koa.js with Jest


Here's a trivial trivial koa server implementation:

const Koa = require('koa')
const api = require('./resources')

const createServer = () => {
  const app = new Koa()

  app.use(api.routes())
  app.use(api.allowedMethods())

  return app
}

module.exports = createServer

And a simple test:

const request = require('supertest')
const createServer = require('../src/server')
const { knex } = require('../src/config/db')

let server

beforeAll(() => {
  server = createServer().listen(8081)
  return server
})

beforeEach(() => {
  return () => {
    knex.migrate.latest()
    knex.seed.run()
  }
})

afterEach(() => {
  return () => {
    knex.migrate.rollback()
  }
})

afterAll(() => {
  server.close()
})

describe('routes: /api/users', () => {
  describe('GET /users', () => {
    test('should return json', async () => {
      const response = await request(server).get('/api/users')
      expect(response.status).toEqual(200)
      expect(response.type).toEqual('application/json')
      expect(response.body).toHaveLength(3)
    })
  })
})

I will skip the example route for brevity sake as the test passes successfully. But then it hangs, and running jest --detectOpenHandles produces:

Jest has detected the following 1 open handle potentially keeping Jest from exiting:

      ●  TCPSERVERWRAP
    
           6 | 
           7 | beforeAll(() => {
        >  8 |   server = createServer().listen(8081)
             |                           ^
           9 |   return server
          10 | })
          11 | 
    
          at Application.listen (node_modules/koa/lib/application.js:82:19)
          at Object.<anonymous> (__tests__/routes.test.js:8:27)

Why does this occur and how can I fix it?


Solution

  • There are several problems.

    return server doesn't affect anything, it can be removed. But beforeAll doesn't wait for the server to start, it should be:

    beforeAll(done => {
      server = createServer().listen(8081, done)
    })
    

    beforeEach and afterEach return functions that are not executed, promises from Knex aren't chained, it should be:

    beforeEach(async () => {
      await knex.migrate.latest()
      await knex.seed.run()
    })
    
    afterEach(async () => {
      await knex.migrate.rollback()
    })
    

    afterAll doesn't wait for server connection to be closed, which is likely the cause for this error, it should be:

    afterAll(done => {
      server.close(done)
    });