Как решить прослушать EADDRINUSE: адрес уже используется в интеграционных тестах - PullRequest
0 голосов
/ 22 марта 2020

Я довольно новичок в Nodejs, и я изучаю Nodejs курс по udemy, я столкнулся с некоторой проблемой listen EADDRINUSE: address already in use :::4000 при повторном запуске интеграционных тестов несколько раз. В первый раз он успешен, но потом я получаю вышеупомянутую ошибку в следующей строке

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

Я вставляю свой индекс. js и два тестовых файла, если кто-то может указать мне, это будет очень помогите мне.

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);
        }
    });
});

Как только я добавлю другой файл для интеграционного теста, подобный приведенному ниже, я начал получите ошибку, которая упоминается после кода этого файла.

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");
        });
  });

Ошибка консоли

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)

Если кто-то может помочь мне, почему он не работает на нескольких запусках, то мне будет очень полезно понять, почему мы должны каждый раз открывать и закрывать объект сервера.

Ответы [ 2 ]

0 голосов
/ 23 марта 2020

Supertest может управлять настройкой / разборкой самого экспресса / koa app, если вы можете импортировать экземпляр app без вызова .listen().

Это включает в себя структурирование кода немного по-другому, поэтому app становится модулем.

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

Тогда точка входа для сервера выполняет listen()

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

Когда супер-тест запускает сервер для app, он будет прослушивать случайный неиспользуемый порт без столкновений.

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

Экземпляр супер-тестового сервера можно использовать и для нескольких тестов

// 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')
  })
})
0 голосов
/ 22 марта 2020

Если я правильно понимаю ваши настройки, у вас есть несколько файлов для тестирования интеграции, которые Jest попытается запустить параллельно (это режим по умолчанию). Получаемая ошибка имеет смысл, поскольку для каждого набора новый экземпляр сервера создается перед каждым тестом, но сервер, возможно, уже был запущен при выполнении другого набора.

Как описано в официальном документация вместо beforeEach имеет смысл использовать globalSetup, где вы должны запустить сервер один раз перед запуском всех тестовых наборов и впоследствии остановить сервер:

// 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",
};

В качестве альтернативы вы можете запустить ваши тесты с опцией --runInBand и beforeAll вместо beforeEach, чтобы убедиться, что перед каждым тестом создается только один сервер, но я бы порекомендовал первый вариант.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...