How to solve listen EADDRINUSE: address already in use in integration tests
Asked Answered
A

4

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.

Acescent answered 22/3, 2020 at 19:59 Comment(1)
Did you get the solution to this problem?Mutule
G
6

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')
  })
})
Greening answered 23/3, 2020 at 2:0 Comment(4)
in this case how and when the server,js will be called or what triggers the listen method to be executed?Acescent
You run node server.js when you want to run the server. The tests don't ever use server.jsGreening
so basically I first run node server.js and then npm test to run the testsAcescent
No need, the tests are self contained. supertest set's up the listen() for you when it is passed an appGreening
R
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"
},
Roots answered 4/8, 2021 at 22:12 Comment(0)
L
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.

Logia answered 22/3, 2020 at 20:26 Comment(4)
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?Acescent
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.Logia
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?Acescent
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 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.Logia
B
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
});
Bryner answered 1/5, 2023 at 10:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.