在我的Express.js开玩笑测试中找不到内存泄漏

发布于 2025-01-25 16:15:29 字数 3214 浏览 2 评论 0 原文

现在,我花了一些时间试图在玩笑测试中找到内存泄漏,即使我成功解决了一些测试,但仍有很多内存从测试套件泄漏到测试套件。具体来说,当i npm测试(所有测试套件)时,我将获得以下输出:

PASS  src/.../suite1.test.ts (71.154 s, 163 MB heap size)
PASS  src/.../suite2.test.ts (59.809 s, 229 MB heap size)
PASS  src/.../suite3.test.ts (9.838 s, 231 MB heap size)
PASS  src/.../suite4.test.ts (7.696 s, 242 MB heap size)
FAIL  src/.../suite5.test.ts (251 MB heap size)
PASS  src/.../suite6.test.ts (10.825 s, 318 MB heap size)
PASS  src/.../suite7.test.ts (19.679 s, 363 MB heap size)
PASS  src/.../suite8.test.ts (14.128 s, 408 MB heap size)
PASS  src/.../suite9.test.ts (16.89 s, 452 MB heap size)

从上面的输出中,如果似乎仍然有一些在测试套件完成后仍然存在的东西。什么是让我烦恼!似乎所有测试套件之间的内存泄漏似乎也可能存在,因为每个套件都留下比最后一个套件更大的内存堆。

这是我所有测试套件的共享测试结构:

import request from "supertest";
import app from "../../app";
import mongoose from "mongoose";
...
some models that I need for testing
...
import faker from "faker";
import eventEmitter from "../../loaders/eventEmitter";

jest.mock("../../some-module");
jest.mock("../../another-module");
jest.mock("axios", () => {
  // Require the original module to not be mocked...
  const originalModule = jest.requireActual("axios");

  return {
    ...originalModule,
    post: jest.fn().mockReturnValue({ data: { access_token: 123, expires_in: 600000 } })
  };
});

describe("TestSuite1", () => {
  beforeEach(() => jest.clearAllMocks());
  beforeAll(async () => await connectDb("TestSuite1"));
  afterEach(async () => await clearDb()); // Function that deletes collections from Mongo
  afterAll(async () => {
    await mongoose.connection.dropDatabase();
    await mongoose.connection.close();
  });

  describe("POST /some-endpoint") {
    beforeEach(async () => {
      myDocument = await buildDocument(); // Builds and saves() to Mongo some document
    });

    it("some assertion", async () => {
      const response = await request(app)
        .post("some-endpoint")
        .send(some-data)
        .set("Accept", "application/json");

      expect(response.status).toEqual(204);
      // more assertions on the response
    });
  }

这里有什么明显的吗?如果需要,我可以添加更多代码。

编辑: 当我使用-Dectopenhandles进行测试时,我也会得到此错误;可能是相关的吗?

  ●  TCPSERVERWRAP

      108 |         it("some assertion", async () => {
      109 |           const response = await request(app)
    > 110 |             .post("some-endpoint")
          |              ^
      111 |             .send({})
      112 |             .set("some-header", header-value)
      113 |             .set("Accept", "application/json");

编辑2: 通过添加以下内容,我尝试在测试套件后关闭服务器:

let server: http.Server;
let agent: SuperAgentTest;

beforeAll(async () => {
  await connectDb("TestSuite1");
  server = app.listen(4000, () => {
    agent = request.agent(server);
  });
});
afterAll(async () => {
  await closeDb();
  jest.resetAllMocks();
  server && server.close();
});

这似乎已解决了开放的手柄问题,但内存泄漏而不是。

另外,为了清楚起见,导入的 eventEmitter 模块可用于以下内容:

const eventEmitter = new EventEmitter();
export default eventEmitter;

I've now spent some time trying to find memory leaks in my Jest tests, and even though I've successfully tackled some, there's still quite a lot of memory being leaked from test suite to test suite. Specifically, when I npm test (all test suites), I get the following output:

PASS  src/.../suite1.test.ts (71.154 s, 163 MB heap size)
PASS  src/.../suite2.test.ts (59.809 s, 229 MB heap size)
PASS  src/.../suite3.test.ts (9.838 s, 231 MB heap size)
PASS  src/.../suite4.test.ts (7.696 s, 242 MB heap size)
FAIL  src/.../suite5.test.ts (251 MB heap size)
PASS  src/.../suite6.test.ts (10.825 s, 318 MB heap size)
PASS  src/.../suite7.test.ts (19.679 s, 363 MB heap size)
PASS  src/.../suite8.test.ts (14.128 s, 408 MB heap size)
PASS  src/.../suite9.test.ts (16.89 s, 452 MB heap size)

From the above output if seems like there is still something that still lives after test suites get finished. The what is what's bugging me! It also seems like a memory leak is probably shared between all the test suites, as every suite is leaving some more memory heap size than the last one.

Here is the shared test structure of all my test suites:

import request from "supertest";
import app from "../../app";
import mongoose from "mongoose";
...
some models that I need for testing
...
import faker from "faker";
import eventEmitter from "../../loaders/eventEmitter";

jest.mock("../../some-module");
jest.mock("../../another-module");
jest.mock("axios", () => {
  // Require the original module to not be mocked...
  const originalModule = jest.requireActual("axios");

  return {
    ...originalModule,
    post: jest.fn().mockReturnValue({ data: { access_token: 123, expires_in: 600000 } })
  };
});

describe("TestSuite1", () => {
  beforeEach(() => jest.clearAllMocks());
  beforeAll(async () => await connectDb("TestSuite1"));
  afterEach(async () => await clearDb()); // Function that deletes collections from Mongo
  afterAll(async () => {
    await mongoose.connection.dropDatabase();
    await mongoose.connection.close();
  });

  describe("POST /some-endpoint") {
    beforeEach(async () => {
      myDocument = await buildDocument(); // Builds and saves() to Mongo some document
    });

    it("some assertion", async () => {
      const response = await request(app)
        .post("some-endpoint")
        .send(some-data)
        .set("Accept", "application/json");

      expect(response.status).toEqual(204);
      // more assertions on the response
    });
  }

Does anything seem obvious here? I can add more code if needed.

Edit:
When I run tests with --detectOpenHandles, I also get this error; could it be relevant?

  ●  TCPSERVERWRAP

      108 |         it("some assertion", async () => {
      109 |           const response = await request(app)
    > 110 |             .post("some-endpoint")
          |              ^
      111 |             .send({})
      112 |             .set("some-header", header-value)
      113 |             .set("Accept", "application/json");

Edit 2:
I made an attempt at closing my server after my test suite, by adding the following:

let server: http.Server;
let agent: SuperAgentTest;

beforeAll(async () => {
  await connectDb("TestSuite1");
  server = app.listen(4000, () => {
    agent = request.agent(server);
  });
});
afterAll(async () => {
  await closeDb();
  jest.resetAllMocks();
  server && server.close();
});

This seems to have fixed the open handle issue, but not the memory leaking.

Also, for clarity, the eventEmitter module that gets imported does the following:

const eventEmitter = new EventEmitter();
export default eventEmitter;

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

小嗲 2025-02-01 16:15:29

让我们尝试分析一些事情:

  • 随着时间的推移,您看到堆的大小增加。
  • 潜在泄漏的测试之前和之后发生的唯一发生的事情是连接到数据库,但是您在最后杀死了连接,因此这不是问题。
  • 您在评论中提到了如何处理buildDocument,并且似乎不会影响泄漏本身。

查看您的代码,我无法立即确定一个问题(这是我很久以前遇到的事情,但并没有太多地挖掘出来)。

我考虑了一点,我认为,唯一的其他可能性是 - 这不是您的错,这可能是Jest's垃圾收藏的问题。

我建议使用 -runinband 进行测试以查看使用/没有它的统计数据(即 jest-logheapusage -runinband )。

此外,如上述问题所示,尝试使用 - expose-gc 露出垃圾收集器,并添加

afterAll(() => {
  global.gc && global.gc()
})

到公共测试设置中,以确保强制垃圾收集。

Let's try to analyze things a little bit:

  • You're seeing heap size increase over time.
  • The only things that happen before and after tests that are potentially leaky are connecting to a database, but you're killing the connection at the end so that's not the issue.
  • You mentioned in the comments how you handle buildDocument and it doesn't seem to affect the leak itself.

Looking at your code, I couldn't identify an issue right away (it's something I've also ran into a long time ago but didn't dig into too deply).

I thought about it a little more and in my opinion, the only other possibility is - it's not your fault and could be an issue with jest's garbage collection.

There has been discussion around this in https://github.com/facebook/jest/issues/7874.

I'd recommend running the test with --runInBand to see stats with/without it (i.e. jest --logHeapUsage --runInBand).

Additionally, as seen in the workaround in the issue above, try exposing the garbage collector with --expose-gc and adding

afterAll(() => {
  global.gc && global.gc()
})

to the common test setup to ensure garbage collection is forced.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文