How to correctly close express server between tests using mocha and chai
Asked Answered
D

3

7

I am looking for a correct way to completely reset my express server between tests! It seems that this is not just a problem for me, many other users have asked the same question, and many blog posts had been written on the argument. The proposed solutions are not working nor satisfactory for me. Here there is another similar question and an article that well describes the problem and suggests some solutions:

Stack overflow Closing an express server after running jasmine specs

Blog: https://glebbahmutov.com/blog/how-to-correctly-unit-test-express-server/

Here a package is specifically created to solve this problem: https://www.npmjs.com/package/server-destroy

Now, a minimal working example to reproduce my condition. In the code under test, an express server is created; when called on some endpoint, the server increments a value and returns it:

( function() {
   'use strict'

   const enableDestroy = require( 'server-destroy' )
   const app = require( 'express' )()
   const http = require( 'http' )

   let val = 0

   app.use( '/inc', (req, res) => {
      val ++
      res.send(val.toString())
   } )

   const server = http.createServer( app )

   server.listen( 3000 )

   enableDestroy(server);

   module.exports = server

} )()

The test consists in two identical test cases; both of them call the server on the endpoint, and check the returned value. before_each and after_each sections are provided in order to assure a new connection is created before the single test case is run, and then closed, to assure independence between the two test cases:

const chai = require( 'chai' )
const chaiHttp = require( 'chai-http' )
const expect = chai.expect
chai.use( chaiHttp )

let server

describe( 'first test group', () => {
   beforeEach( () => {
      server = require( './server' )
   } ),

   afterEach( ( done ) => {
      server.destroy( done )
      delete require.cache[require.resolve( './server' )]
   } ),

   it( 'should respond 1', ( done ) => {
      chai.request( server )
         .get( '/inc' )
         .set( 'Connection', 'close' )
         .end( ( err, res ) => {
            expect( res.text ).to.be.equal( '1' )
            done()
         } )
   } ),

   it( 'should respond 1', ( done ) => {
      chai.request( server )
         .get( '/inc' )
         .set( 'Connection', 'close' )
         .end( ( err, res ) => {
            expect( res.text ).to.be.equal( '1' )
            done()
         } )
   } )
} )

The test fails because the server is not running after the first test. Please note that in the after_each section I forced a cache cleaning for lost completely the last server instance. The test succeeds if a single test case is run:

first test group
    ✓ should respond 1
    1) "after each" hook for "should respond 1"


  1 passing (70ms)
  1 failing

  1) first test group
       "after each" hook for "should respond 1":
     Error: Not running
      at Server.close (net.js:1620:12)
      at emitCloseNT (net.js:1671:8)
      at _combinedTickCallback (internal/process/next_tick.js:135:11)
      at process._tickCallback (internal/process/next_tick.js:180:9)

The configuration I used:

  • Node 8.11.1
  • Express: "4.16.3"
  • Mocha 5.0.5
  • chai 4.1.2
  • chai-http 4.0.0

How can I solve this problem? And what does the error message mean?

Defensible answered 12/4, 2018 at 6:54 Comment(3)
Usually, testing is done with a web server proxy and not with a server. Is there any reason that you cannot use a proxy for express like supertest?Antilogism
I think that I'm using a proxy, in my case should be chai-http isn't it?Defensible
In afterEach, you are destroying the server and quite possible that before the server starts again, the second test is run. 1. Try using a timeout before running the second test. 2. Use keepOpen as in const chaiHttp = chai.request(app).keepOpen() (this line is from the chai-http docs) to keep the server open. In any case, remove both the afterEach block and the .set('connection',close) line, as they're not doing anything useful for your testsAntilogism
M
8

It works with a little modification I didn't use server-destroy since server.close works just fine

server.js

// ( function() { // no need for this
   'use strict'

   //const enableDestroy = require( 'server-destroy' )
   const app = require( 'express' )()
   const http = require( 'http' )

   let val = 0

   app.use( '/inc', (req, res) => {
      val ++
      res.send(val.toString())
   } )

   const server = http.createServer( app )

   server.listen( 3000 )

   // enableDestroy(server);    
   module.exports = server

// } )()

test.js

const chai = require( 'chai' )
const chaiHttp = require( 'chai-http' )
const expect = chai.expect
chai.use( chaiHttp )

let server

describe( 'first test group', () => {
   beforeEach( () => {
      server = require( './server' )
   } ),

   afterEach( ( done ) => {

      // UPDATE DON'T CLOSE THE SERVER

      delete require.cache[require.resolve( './server' )]
      done()

      //server.close( () => {
      //   delete require.cache[require.resolve( './server' )]
      //   done()
      //})      
   } ),

   it( 'should respond 1', ( done ) => {
      chai.request( server )
         .get( '/inc' )
         .set( 'Connection', 'close' )
         .end( ( err, res ) => {
            expect( res.text ).to.be.equal( '1' )
            done()
         } )
   } ),

   it( 'should respond 1', ( done ) => {
      chai.request( server )
         .get( '/inc' )
         .set( 'Connection', 'close' )
         .end( ( err, res ) => {
            expect( res.text ).to.be.equal( '1' )
            done()
         } )
   } )
} )
Malcom answered 12/4, 2018 at 7:58 Comment(3)
Thank you! This work I give you an up-vote but I think that can't be the right solution: the cb you passed to close ignores the arguments (the error in this case). You can check simply logging the argument passed to the close cb: the error is still present here! server.close( (arg) => { console.log( arg ) delete require.cache[require.resolve( './server' )] done() })Defensible
You are right, so i updated my code. Don't call server.close. The server will be closed automaticly, i don't know why though. You can check that it's closing by adding this line server.on( 'close', () => console.log('Closing') ) just bellow server.listen in server.js file.Malcom
Yes, It's right! Seems that someone close the server for me, so there is no more the necessity to manually close the server but remain the constraints to delete the cache if you want refresh the server instance! So If you update your response with this news I will accept it!Defensible
B
1

this is after some time from the initial question.

On the chai-http documentation i could able to find that it has mechanism to open up the server connection using the keepOpen() method from the request method. (actually it returns back an agent)

const chai = require("chai");
const chaiHttp = require("chai-http");

chai.use(chaiHttp);

const chaiAppServer = chai.request(server).keepOpen();

https://www.chaijs.com/plugins/chai-http/#integration-testing

But as it mentions, we have to manually close the server connection.

describe("TEST Case group", () => {

    after(() => {
        chaiAppServer.close();
    });

    //..... integration test cases

});
Broek answered 9/12, 2020 at 19:52 Comment(0)
D
0

I just had to add --exit to the mocha command running the tests. No after() block needed.

my package.json script:

"test:integration": "mocha --timeout 5000 --exit -r ts-node/register test/integration/*.spec.ts",

The only things needed in my test scripts is the before() function, no cleanup needed.

import chai, { expect } from 'chai';
import chaiHttp from 'chai-http';

chai.use(chaiHttp);
describe.only('some-test', function () {
  let requestor: ChaiHttp.Agent;

  before(function (done) {
    requestor = chai.request(app).keepOpen();
    done();
  });
...
Donner answered 24/5 at 10:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.