Jest.js 如何解决listen EADDRINUSE:集成测试中已使用的地址

inkz8wg9  于 2023-05-04  发布在  Jest
关注(0)|答案(4)|浏览(154)

我是Nodejs的新手,我正在udemy上学习Nodejs课程,我在多次重新运行集成测试时遇到了一些listen EADDRINUSE: address already in use :::4000的问题。第一次成功了,但是后来我在下面的行中得到了上面提到的错误

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

我正在粘贴我的index.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)

如果有人能帮助我为什么它在多次运行时失败,那么它将真正有助于我理解为什么我们需要每次打开和关闭服务器对象。

2ic8powd

2ic8powd1#

Supertest能够管理express/koa app本身的安装/拆卸,如果您可以导入app的示例而无需在其上调用.listen()
这涉及到代码的结构略有不同,因此app成为一个模块,与服务器.listen()分开

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

然后,运行服务器的入口点导入app,然后使用.listen()设置服务器

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

当supertest使用导入的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')
  })
})
7xllpg7q

7xllpg7q2#

一种解决方案是运行jest,将max worker指定为1,可以在package.json中按以下方式配置:

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

tcomlyy63#

如果我对您的设置理解正确的话,您有多个集成测试文件,Jest将尝试并行运行这些文件(这是默认模式)。您得到的错误是有意义的,因为对于每个套件,在每个测试之前都创建了一个新的服务器示例,但是在执行不同的套件时,服务器可能已经启动。
正如官方文档中所描述的,使用globalSetup而不是beforeEach是有意义的,在运行所有测试套件之前,你会初始化你的服务器一次,然后停止服务器:

// 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来运行测试,以确保在每次测试之前只创建一个服务器,但我建议使用第一个选项。

r7xajy2e

r7xajy2e4#

另一种方法可能是在测试期间侦听随机未使用的端口(端口0),如果您需要在测试期间运行服务,例如:

const server = http.createServer(expressApp);
if (mode == 'test') { port = 0; }
server.listen(port, () => {
  process.env.PORT = server.address().port; // if you need it later
});

相关问题