typegoose / typegoose/mongodb-memory-server

MongoMemoryServer.stop() is not a fully optimized teardown solution

Open
#1,032 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug released on @beta
Dominant language
TypeScript
Stars
2.8k
Forks
191
Avg merge
4h 25m
Merged PRs (30d)
10

Description

Versions

  • NodeJS: v24.19.0
  • mongodb-memory-server-*: 11.2.0
  • mongodb(the binary version): 0.0.0
  • mongodb(the js package): n/a, not used directly by the repro
  • system: Linux (GitHub Actions ubuntu-24.04 runner)

package: mongodb-memory-server-core

What is the Problem?

The documented lifecycle pattern for MongoMemoryServer — one instance, reused across tests, cleaned up with a single .stop() call at the end — is what docs/guides/integration-examples/test-runners.md recommends, and it's the same shape this project's own MongoMemoryServer.test.ts already uses (describe('getUri()', ...), plain afterAll(() => mongoServer.stop())).

Here I've created a new baseline test, a reasonably simple situation: the MongoMemoryServer mongod process is killed after the server has been created. In this situation, the documented .stop() call is not enough: it throws instead of succeeding.

That raises a bigger question than just this one code path: what's actually the process for managing a MongoMemoryServer instance's lifecycle in a real application? If the current testing suite is the best documentation available, it's clear that there is at least one (and likely many more) scenarios where a simple .stop() call is not sufficient.

Code Example

// mirrors describe('getUri()', ...)'s beforeAll/afterAll shape already in this file
describe('afterAll cleanup after mongod is killed unexpectedly', () => {
  let mongoServer: MongoMemoryServer;
  let dbPath: string;

  beforeAll(async () => {
    mongoServer = await MongoMemoryServer.create();
    dbPath = mongoServer.instanceInfo!.dbPath;
  });

  // unchanged from getUri() above -- the plain, documented pattern
  afterAll(async () => {
    if (mongoServer) {
      await mongoServer.stop();
    }
  });

  // consequence 1: was the temp directory actually cleaned up?
  afterAll(async () => {
    expect(await utils.statPath(dbPath)).toBeUndefined();
  });

  // consequence 2: can the object be recovered by restarting it?
  afterAll(async () => {
    await mongoServer.start();
  });

  it('mongod is killed unexpectedly between tests', async () => {
    const instance = mongoServer.instanceInfo!.instance;
    const pid = instance.mongodProcess!.pid;

    // wait for this library's own internal crash-recovery to fully settle first
    const closedPromise = new Promise<void>((resolve) => {
      instance.once(MongoInstanceEvents.instanceClosed, () => resolve());
    });
    process.kill(pid!, 'SIGKILL'); // simulate a crash
    await closedPromise;
    expect(instance.stopPromise).toBeDefined();
    await instance.stopPromise!;
  });
});

Debug Output

Debug Output
FAIL src/__tests__/MongoMemoryServer.test.ts

  ● Test suite failed to run

    Cannot cleanup because "instance.mongodProcess" is still defined

      at MongoMemoryServer.cleanup (src/MongoMemoryServer.ts:641:7)
      at MongoMemoryServer.cleanup [as stop] (src/MongoMemoryServer.ts:600:18)
      at Object.<anonymous> (src/__tests__/MongoMemoryServer.test.ts:865:9)

  ● Test suite failed to run

    expect(received).toBeUndefined()

    Received: {"atimeMs": 1788978155240.21, "birthtimeMs": 1788978155240.21, "blksize": 4096,
    "blocks": 8, "ctimeMs": 1788978155348.21, "dev": 2049, "gid": 1001, "ino": 8957260,
    "mode": 16832, "mtimeMs": 1788978155348.21, "nlink": 4, "rdev": 0, "size": 4096, "uid": 1001}

      at Object.<anonymous> (src/__tests__/MongoMemoryServer.test.ts:871:44)

  ● Test suite failed to run

    Cannot start because "instance.mongodProcess" is already defined!

      at MongoMemoryServer.start (src/MongoMemoryServer.ts:345:7)
      at Object.<anonymous> (src/__tests__/MongoMemoryServer.test.ts:876:25)

Tests:       64 passed, 64 total

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the failure in src/tests/MongoMemoryServer.test.ts using the killed mongod scenario and inspect MongoMemoryServer.stop(), cleanup(), and start() in src/MongoMemoryServer.ts. Compare the behavior with docs/guides/integration-examples/test-runners.md. Done should establish whether stop removes the temporary directory and whether the instance can be restarted after an unexpected mongod exit, with regression coverage for the agreed lifecycle behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
mongodb, nodejs, typescript
Domain
backend, testing-qa
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.