1

I am pretty new to Nodejs and i am learning Nodejs course on udemy, I am facing some trouble of listen EADDRINUSE: address already in use :::4000 while re-running integration tests multiple time. The first time its successful but afterward I am getting the above-mentioned error on the following line

const server = app.listen(port, () => {winston.info(`Listening on port ${port}`)});

I am pasting my index.js and two test files, if some can point me out it will be a great help for me.

Index.js

    const Joi = require("@hapi/joi");
    Joi.objectId = require("joi-objectid")(Joi);
    const winston = require("winston");
    const express = require("express");
    const app = express();

    require("./startup/logging")();
    require("./startup/config")();
    require("./startup/dbconnectivity")();
    require("./startup/routes")(app);
    const port = process.env.port || 4000;
    const server = app.listen(port, () => {winston.info(`Listening on port ${port}`)});
    // exporting server object to be used in integration tests.
    module.exports = server;


**Integration test file for Genre**

const request = require("supertest");
let server;
const {Genere} = require("../../models/genere");
const {User} = require("../../models/user");

describe("/api/genere", () => {
    beforeEach(() => {
        console.log("Before each Genre");
        server = require("../../index");
    });
    afterEach(async () => {
        console.log("After each Genre");
        await Genere.deleteMany({});
        await server.close();
    });

    describe("/GET", () => {
        it("should return list of generes", async() => {
            await Genere.insertMany([
                {name: "genre1"},
                {name: "genre2"},
                {name: "genre3"}
            ]);
            const res = await request(server).get("/api/geners");
            expect(res.status).toBe(200);
            console.log("response body is : " + res.body);
            expect(res.body.length).toBe(3);
            expect(res.body.map(g => g.name)).toContain("genre1");
        });
    });

    describe("/GET/:id", () => {
        it("should return genre with id", async() => {
            const genre = new Genere({name: "genre1"});
            await genre.save();
            const res = await request(server).get("/api/geners/"+ genre.id);
            expect(res.status).toBe(200);
            expect(res.body.name).toBe("genre1");
        });

        it("should return error with invalid id", async() => {
            const genre = new Genere({name: "genre1"});
            await genre.save();
            const res = await request(server).get("/api/geners/1");
            expect(res.status).toBe(404);
            expect(res.text).toMatch(/Invalid/);

        });
    });

    describe("/POST", () => {
        it("should return 401 if not authorized", async() => {
            const genere = new Genere({name: "genere1"});
            const res = await request(server).post("/api/geners").send(genere);
            expect(res.status).toBe(401);
        });

        it("should return 400 if the name is less than 4 chars", async() => {
            const res = await createRequestWithGenre({name: "g1"});
            expect(res.status).toBe(400);
        });

        it("should return 400 if the name is greater than 25 chars", async() => {
            const genreName = Array(26).fill("a").join("");
            const res = await createRequestWithGenre({name: genreName})
            expect(res.status).toBe(400);
        });

        it("should return 201 with gener object if proper object is sent", async() => {
            const res = await createRequestWithGenre({name: "genre1"})
            expect(res.status).toBe(201);
            expect(res.body).toHaveProperty("_id");
            expect(res.body).toHaveProperty("name", "genre1");

            const genre = await Genere.find({ name: "genre1"});
            expect(genre).not.toBe(null);
        });

        async function createRequestWithGenre(genre) {
            const token = new User().generateAuthToken();
            return await request(server)
            .post("/api/geners")
            .set("x-auth-token", token)
            .send(genre);
        }
    });
});

As soon as i add another file for integration test like the one below i started to get the error which is mentioned after this file code.

const {User} = require("../../models/user");
 const {Genere} = require("../../models/genere");
 const request = require("supertest");
let token;

 describe("middleware", () => {

        beforeEach(() => {
            console.log("Before each Middleware");
            token = new User().generateAuthToken();
            server = require("../../index");
        });

        afterEach(async () => {
            console.log("After each Middleware");
            await Genere.deleteMany({});
            await server.close();
        });

        const exec = async() => {
            return await request(server)
            .post("/api/geners")
            .set("x-auth-token", token)
            .send({name: "gener1"});
        }

         it("should return 400 if invalid JWT token is sent", async() => {
            token = "invalid_token";
            const res = await exec();
            expect(res.status).toBe(400); 
            expect(res.text).toBe("Invalid auth token");
        });
  });

Console Error

middleware
    ✕ should return 400 if invalid JWT token is sent (510ms)

  ● middleware › should return 400 if invalid JWT token is sent

    listen EADDRINUSE: address already in use :::4000

      10 | require("./startup/routes")(app);
      11 | const port = process.env.port || 4000;
    > 12 | const server = app.listen(port, () => {winston.info(`Listening on port ${port}`)});
         |                    ^
      13 | // exporting server object to be used in integration tests.
      14 | module.exports = server;

      at Function.listen (node_modules/express/lib/application.js:618:24)
      at Object.<anonymous> (index.js:12:20)
      at Object.beforeEach (tests/integration/middleware.test.js:11:22)

If someone can help me why it fails on the multiple runs then it will be really helpful for me to understand why do we need to open and close server object every time.

skyboyer
  • 22,209
  • 7
  • 57
  • 64
Madu
  • 4,849
  • 9
  • 44
  • 78

4 Answers4

5

Supertest is able to manage the setup/teardown of an express/koa app itself if you can import an instance of app without calling .listen() on it.

This involves structuring the code a little differently so app becomes a module, separate to the server .listen()

// app.js module
const app = require('express')()
require("./startup/logging")()
...
module.exports = app

Then the entrypoint for running the server imports the app then sets up the server with .listen()

// server.js entrypoint
const app = require('./app')
const port = process.env.port || 4000;
app.listen(port, () => {winston.info(`Listening on port ${port}`)});

When supertest uses the imported app, it will start its own server and listen on a random unused port without clashes.

// test
const request = require('supertest')
const app = require('./app')
request(app).get('/whatever')

The supertest "server" instance can be reused for multiple tests too

// reuse test
const supertest = require('supertest')
const app = require('./app')

describe('steps', () => {
  const request = supertest(app)
  it('should step1', async() => {
    return request.get('/step1')
  })
  it('should step2', async() => {
    return request.get('/step2')
  })
})
Matt
  • 68,711
  • 7
  • 155
  • 158
  • in this case how and when the server,js will be called or what triggers the listen method to be executed? – Madu Mar 25 '20 at 15:25
  • You run `node server.js` when you want to run the server. The tests don't ever use server.js – Matt Mar 25 '20 at 15:42
  • so basically I first run ``node server.js`` and then ``npm test`` to run the tests – Madu Mar 25 '20 at 15:57
  • 1
    No need, the tests are self contained. supertest set's up the `listen()` for you when it is passed an `app` – Matt Mar 26 '20 at 01:04
1

One solution is to run jest with max workers specified to 1 which can be configured in your package.json in the following way:

"scripts": { 
    "test": "NODE_ENV=test jest --forceExit --detectOpenHandles --watchAll --maxWorkers=1"
},
0

If I understand your setup correctly, you have multiple intergration-test files which Jest will try to run in parallel (this is the default-mode). The error you're getting makes sense, since for each suite a new server instance is created before each test, but the server might already have been started while executing a different suite.

As described in the offical documentation instead of beforeEach it would make sense to use globalSetup where you would init your server once before running all test suites and stop the server afterwards:

// setup.js
module.exports = async () => {
  // ...
  // Set reference to your node server in order to close it during teardown.
  global.__MY_NODE_SERVER__ = require("../../index");
};

// teardown.js
module.exports = async function() {
  await global.__MY_NODE_SERVER__.stop();
};


// in your jest-config you'd set the path to these files:
module.exports = {
  globalSetup: "<rootDir>/setup.js",
  globalTeardown: "<rootDir>/teardown.js",
};

Alternatively you could run your tests with the --runInBand option and beforeAll instead of beforeEach in order to make sure that only one server is created before each test, but I'd recommend the first option.

eol
  • 23,236
  • 5
  • 46
  • 64
  • Hi, thanks. I don't understand why before each suite a new server instance is created before each test? I am just using the exported server object by using server = require("../../index") which should not call listen method or my understanding is not correct? – Madu Mar 25 '20 at 10:37
  • In the code above I see `describe("middleware", () => { ...}` and `describe("/api/genere", () => {...}`. In both you use `server = require("../../index")` which is why two server instances are created. – eol Mar 25 '20 at 11:08
  • may be my understanding of ``require`` is not correct, so when i run ``server = require("../../index");`` AFAIK it should only give me the exported ``server`` object, why does it also executes ``listen`` method? – Madu Mar 25 '20 at 16:01
  • Because the thing you export as `server` is already an actual http-server listening for connections. `app.listen(..)` creates the server and binds the specified host/port -> see https://expressjs.com/en/api.html#app.listen By requiring the index-module all the code in there is executed, in order for you to be able to export the server object. – eol Mar 25 '20 at 19:14
0

Another approach may be to listen a random unused port (port 0) during tests, if you need the service running during tests, for example:

const server = http.createServer(expressApp);
if (mode == 'test') { port = 0; }
server.listen(port, () => {
  process.env.PORT = server.address().port; // if you need it later
});
João Reis
  • 423
  • 4
  • 11