nodejs / nodejs/node

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

Aperta
#59,163 9 commenti 8 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

never-stale test_runner
Lingua principale
JavaScript
Stelle
122k
Fork
37.3k
Merge medio
4g 2h
PR unite (30g)
283

Descrizione

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?

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Direzione di ricerca

Inizia eseguendo la riproduzione in test/cache-issue.test.js con node --test --experimental-test-module-mocks. Leggi il comportamento di node:test relativo al mocking dei moduli esercitato da t.mock.module() e mock.restoreAll(), insieme agli import di file-processor.js e fs-wrapper.js. Il lavoro è completato quando il secondo test riceve il proprio mock senza il parametro di query per aggirare la cache e i test esistenti continuano a passare.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
javascript, node.js
Ambito
backend, testing
Tipo di issue
Bug
Difficoltà
4/5
Tempo stimato
3-5 giorni
Stato di attività
Tranquilla
Chiarezza
Abbastanza chiara
Idoneità per principianti
48/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.