nodejs / nodejs/node

experimental-test-module-mocks with ES imports does not reset mocked modules for tests in the same file

Offen
#59,163 9 Kommentare 8 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

never-stale test_runner
Vorherrschende Sprache
JavaScript
Sterne
122k
Forks
37.3k
Ø Merge
4 T. 2 Std.
Gemergte PRs (30 T.)
283

Beschreibung

Version

24.4.1

Platform
Microsoft Windows NT 10.0.26100.0 x64
Subsystem

node:test

What steps will reproduce the bug?
// file1: utils/fs-wrapper.js
// Simple wrapper around fs.readFileSync to demonstrate the issue
import { readFileSync } from 'node:fs';

export default function readFile(path: string): string {
    return readFileSync(path, 'utf8');
}

// file2: utils/file-processor.js
// Module that depends on fs-wrapper
import readFile from './fs-wrapper.js';

export default function processFile(path: string): string {
    const content = readFile(path);
    return content.toUpperCase();
}

// file3: test/cache-issue.test.js
// Test demonstrating the ES module cache issue
import { test, describe, beforeEach, mock } from 'node:test';
import assert from 'node:assert/strict';

describe('ES Module Cache Issue with mock.restoreAll()', () => {
    beforeEach(() => {
        mock.restoreAll();
    });

    test('first test - should use mock', async (t) => {
        // Mock the fs-wrapper module
        const mockReadFile = mock.fn(() => 'mocked content from test 1');
        t.mock.module('../utils/fs-wrapper.js', {
            defaultExport: mockReadFile,
        });

        // Import the module that depends on fs-wrapper
        const fileProcessorModule = await import('../utils/file-processor.js');
        const processFile = fileProcessorModule.default;

        const result = processFile('dummy-path.txt');

        assert.strictEqual(result, 'MOCKED CONTENT FROM TEST 1');
        assert.strictEqual(mockReadFile.mock.callCount(), 1);
    });

    test('second test - should use different mock but fails due to cache', async (t) => {
        // Mock with different return value
        const mockReadFile = mock.fn(() => 'mocked content from test 2');
        t.mock.module('../utils/fs-wrapper.js', {
            defaultExport: mockReadFile,
        });

        // This import returns cached version that still references the old mock
        const fileProcessorModule = await import('../utils/file-processor.js');
        const processFile = fileProcessorModule.default;

        const result = processFile('dummy-path.txt');

        // This assertion will fail because the cached module still uses the old mock
        assert.strictEqual(result, 'MOCKED CONTENT FROM TEST 2');
        assert.strictEqual(mockReadFile.mock.callCount(), 1);
    });

    test('third test - workaround with cache busting', async (t) => {
        // Mock with third return value
        const mockReadFile = mock.fn(() => 'mocked content from test 3');
        t.mock.module('../utils/fs-wrapper.js', {
            defaultExport: mockReadFile,
        });

        // Use cache busting query parameter to force fresh import
        const fileProcessorModule = await import('../utils/file-processor.js?v=' + Date.now());
        const processFile = fileProcessorModule.default;

        const result = processFile('dummy-path.txt');

        // This assertion passes because we bypassed the cache
        assert.strictEqual(result, 'MOCKED CONTENT FROM TEST 3');
        assert.strictEqual(mockReadFile.mock.callCount(), 1);
    });
});

// Command line: node --test --experimental-test-module-mocks
How often does it reproduce? Is there a required condition?

Every time.

What is the expected behavior? Why is that the expected behavior?

The mocked module should reset between tests.

What do you see instead?
▶ ES Module Cache Issue with mock.restoreAll()
  ✔ first test - should use mock (43.5241ms)
  ✖ second test - should use different mock but fails due to cache (3.0297ms)
  ✔ third test - workaround with cache busting (4.2197ms)
✖ ES Module Cache Issue with mock.restoreAll() (52.1751ms)
ℹ tests 6
ℹ suites 1
ℹ pass 5
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 137.1057

✖ failing tests:

test at dist\tests\file-processor.test.js:22:5
✖ second test - should use different mock but fails due to cache (3.0297ms)
  AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
  actual expected

      at TestContext.<anonymous> (file:///C:/Users/xxx/Documents/GitHub/genesys-concepts/dist/tests/file-processor.test.js:34:16)
      at async Test.run (node:internal/test_runner/test:1069:7)
      at async Suite.processPendingSubtests (node:internal/test_runner/test:752:7) {
    generatedMessage: true,
    code: 'ERR_ASSERTION',
    actual: 'MOCKED CONTENT FROM TEST 1',
    expected: 'MOCKED CONTENT FROM TEST 2',
    operator: 'strictEqual'
  }
Additional information

Not sure if there is a better workaround for this or maybe I'm doing something wrong?

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Beginne damit, die Reproduktion in test/cache-issue.test.js mit node --test --experimental-test-module-mocks auszuführen. Lies das von t.mock.module() und mock.restoreAll() getestete node:test-Verhalten beim Mocking von Modulen sowie die Imports von file-processor.js und fs-wrapper.js. Fertig ist die Änderung, wenn der zweite Test seinen eigenen Mock ohne den Cache-Busting-Query-Parameter erhält und die bestehenden Tests weiterhin erfolgreich sind.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
javascript, node.js
Bereich
backend, testing
Issue-Typ
Bug
Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Aktivitätsstatus
Ruhig
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
48/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.