Skip to content

Fix compatibility with Node 20 #384

Closed

Description

@ademdinarevic

Since we upgraded to node 20.10.0 the method for mocking file creation doesn't work properly

const mockFs = require("mock-fs");

mockFs({
  "/file": {
     VAR: "var_value",
  },
});

Activity

  1. zbigg commented on Dec 22, 2023

    @zbigg

    For anyone trying to fix it, it's caused by node20 to use new binding method: readFileUtf8 which isn't "mocked" by this module.

    It looks like we should just implement open+read+toUtf+close as one function here.

    The usage:

    https://github.com/nodejs/node/blob/a81788cb27b96579343765eeba07fd2b772829d0/lib/fs.js#L456

    Implementation

    https://github.com/nodejs/node/blob/a81788cb27b96579343765eeba07fd2b772829d0/src/node_file.cc#L2375

    This is very naive and PoC binding that fixes readFileSync(..., 'utf8') for node 20

    Binding.prototype.readFileUtf8 = function (
      name,
      flags
    ) {
        const fd = this.open(name, flags);
        const descriptor = this.getDescriptorById(fd);
    
        if (!descriptor.isRead()) {
          throw new FSError('EBADF');
        }
        const file = descriptor.getItem();
        if (file instanceof Directory) {
          throw new FSError('EISDIR');
        }
        if (!(file instanceof File)) {
          // deleted or not a regular file
          throw new FSError('EBADF');
        }
        const content = file.getContent();
        return content.toString('utf8');
    };
    
  2. canhassancode commented on Dec 22, 2023

    @canhassancode

    Facing the exact same issue, it was working completely fine then randomly broke. Below is an example of the setup:

    beforeAll(() => {
      mockFs.restore();
    });
    
    afterEach(() => {
      mockFs.restore();
    });
    
    it('mock test', () => {
      mockFs({
        './profiles': {
          test: {
            'TEST_PROFILE_1.json': JSON.stringify(testProfile1),
            'TEST_PROFILE_2.json': JSON.stringify(testProfile2),
          },
    ...
        },
      });
    });
    

    Getting the error: ENOENT: no such file or directory, open 'profiles/test/TEST_PROFILE_2.json'

  3. crfrolik commented on Jan 30, 2024

    @crfrolik

    For those like me who came here in a panic because their tests started failing, I had success (and minimal effort) replacing mock-fs with memfs.

  4. PedroS11 commented on Feb 6, 2024

    @PedroS11

    For those like me who came here in a panic because their tests started failing, I had success (and minimal effort) replacing mock-fs with memfs.

    Do you mind post some examples please? I'm trying to use memfs but I'm struggling with it. I found an basic example with jest however, i'm using chai so nothing is working

    For reference, this is the basic example of memfs using jest: https://medium.com/nerd-for-tech/testing-in-node-js-easy-way-to-mock-filesystem-883b9f822ea4

    or https://dev.to/julienp/node-js-testing-using-a-virtual-filesystem-as-a-mock-2jln

  5. JBloss1517 commented on Feb 24, 2024

    @JBloss1517

    @PedroS11 I also decided to switch using memfs. For me, it was mostly a matter of switching mockFS({. . .}) with vol.fromNestedJSON({. . .}) and changing mockFS.restore() to vol.reset()

    So for example (I am using vitest in this but jest should be pretty close as well)

    Using mock-fs

    const mockFS = require("mock-fs");
    import { afterEach, beforeEach, describe, expect, test } from "vitest";
    
    describe("some test", () => {
      beforeEach(() => {
        mockFS({
          Projects: {
            "Projects - 23": {
              "Test Client": {
                "Projects 1": {
                  "Sub folder": {},
                },
              },
            },
            "Projects - 24": {},
          },
        });
      });
    
      afterEach(() => {
        mockFS.restore();
      });
    
      test("project folder creation", () => {
        const projectFolderPath = path.join(
          "Projects",
          "Projects - 23",
          "Test Client",
          "Projects 2",
        );
        const result = _createProjectFolder(projectFolderPath, projectTemplatePath);
        expect(result).toEqual({ path: projectFolderPath, error: null });
      });
    });

    Using memfs

    import { fs, vol } from "memfs";
    import { afterEach, beforeEach, describe, expect, test } from "vitest";
    
    describe("some test", () => {
      beforeEach(() => {
        vol.fromNestedJSON({   // <-- Changed this from mockFS
          Projects: {
            "Projects - 23": {
              "Test Client": {
                "Projects 1": {
                  "Sub folder": {},
                },
              },
            },
            "Projects - 24": {},
          },
        });
      });
    
      afterEach(() => {
        vol.reset(); // <-- changed this from mockFS.restore();
      });
    
      test("project folder creation", () => {
        const projectFolderPath = path.join(
          "Projects",
          "Projects - 23",
          "Test Client",
          "Projects 2",
        );
        const result = _createProjectFolder(projectFolderPath, projectTemplatePath);
        expect(result).toEqual({ path: projectFolderPath, error: null });
      });
    });

    I hope that helps!

  6. PedroS11 commented on Feb 24, 2024

    @PedroS11

    @PedroS11 I also decided to switch using memfs. For me, it was mostly a matter of switching mockFS({. . .}) with vol.fromNestedJSON({. . .}) and changing mockFS.restore() to vol.reset()

    So for example (I am using vitest in this but jest should be pretty close as well)

    Using mock-fs

    const mockFS = require("mock-fs");
    import { afterEach, beforeEach, describe, expect, test } from "vitest";
    
    describe("some test", () => {
      beforeEach(() => {
        mockFS({
          Projects: {
            "Projects - 23": {
              "Test Client": {
                "Projects 1": {
                  "Sub folder": {},
                },
              },
            },
            "Projects - 24": {},
          },
        });
      });
    
      afterEach(() => {
        mockFS.restore();
      });
    
      test("project folder creation", () => {
        const projectFolderPath = path.join(
          "Projects",
          "Projects - 23",
          "Test Client",
          "Projects 2",
        );
        const result = _createProjectFolder(projectFolderPath, projectTemplatePath);
        expect(result).toEqual({ path: projectFolderPath, error: null });
      });
    });

    Using memfs

    import { fs, vol } from "memfs";
    import { afterEach, beforeEach, describe, expect, test } from "vitest";
    
    describe("some test", () => {
      beforeEach(() => {
        vol.fromNestedJSON({   // <-- Changed this from mockFS
          Projects: {
            "Projects - 23": {
              "Test Client": {
                "Projects 1": {
                  "Sub folder": {},
                },
              },
            },
            "Projects - 24": {},
          },
        });
      });
    
      afterEach(() => {
        vol.reset(); // <-- changed this from mockFS.restore();
      });
    
      test("project folder creation", () => {
        const projectFolderPath = path.join(
          "Projects",
          "Projects - 23",
          "Test Client",
          "Projects 2",
        );
        const result = _createProjectFolder(projectFolderPath, projectTemplatePath);
        expect(result).toEqual({ path: projectFolderPath, error: null });
      });
    });

    I hope that helps!

    Thank you for this snippet @JBloss1517 !! in the following weeks, I'll try to migrate too and I'll see how it works. I'll leave my progress here when I start so it helps everyone with this struggle

  7. dennismeister93 commented on Mar 10, 2024

    @dennismeister93

    hi @tschaub, are there any plans to work on this?

  8. tschaub commented on Mar 18, 2024

    @tschaub
    Owner

    @dennismeister93 - I would be happy to review a pull request, but don't have plans to spend time on a fix myself.

  9. dicolasi commented on Apr 16, 2024

    @dicolasi

    struggling with the same problem here.

  10. MichaelSitter commented on May 7, 2024

    @MichaelSitter

    I was having issues with my Jest tests still using the built-in fs instead of the memfs implementation. This worked to mock out fs & fs/promises:

    import { fs, vol } from 'memfs'
    // import needs to match what you used in your code
    jest.mock('fs', () => fs)
    jest.mock('fs/promises', () => fs.promises)
    
    // test code
    
  11. bcass commented on Jun 17, 2024

    @bcass

    Here's an example of migrating a mock-fs test that used mock.load() to memfs. The test itself doesn't change. Just adding a vi.mock() and changing the beforeEach() and afterEach().

    Before with mock-fs:

    import mock from 'mock-fs'
    import fs from 'fs'
    import { describe, test, expect, beforeEach, afterEach } from 'vitest'
    
    describe('test', () => {
      beforeEach(() => {
        mock({
          test: {
            'UnitTestInput.xlsx': mock.load(path.resolve(__dirname, '../test/UnitTestInput.xlsx'), { lazy: false })
            another: {
              path: {
                'data.json': '{ "fakeData": "yes" }'
              }
            }
          }
        })
      })
      afterEach(() => {
        mock.restore()
      })
      test('myUtility.run() processes test file and creates output', () => {
        // given:
        const myUtility = new MyUtility()
    
        // when: we run the utility
        myUtility.run(`${process.cwd()}/test/UnitTestInput.xlsx`)
    
        // then: output is generated and matches snapshot
        expect(fs.readFileSync(`${process.cwd()}/test/another/path/data-generated.json`, 'utf8')).toMatchSnapshot()
        expect(fs.existsSync(`${process.cwd()}/test/report-UnitTestInput.txt`)).toBeTruthy()
      })
    })
    

    After with memfs:

    import { vol, fs } from "memfs";
    import { vi, describe, test, expect, beforeEach, afterEach } from 'vitest'
    
    // Mock fs everywhere else with the memfs version.
    vi.mock('fs', async () => {
      const memfs = await vi.importActual('memfs')
    
      // Support both `import fs from "fs"` and "import { readFileSync } from "fs"`
      return { default: memfs.fs, ...memfs.fs }
    })
    
    describe('test', () => {
      beforeEach(async () => {
        // Get the real fs method, read the real file, and inject into the memory file system.
        const fs = await vi.importActual('fs')
        const unitTestInputXlsx = fs.readFileSync(path.resolve(__dirname, '../test/UnitTestInput.xlsx'))
    
        vol.fromNestedJSON({
          test: {
            'UnitTestInput.xlsx': unitTestInputXlsx,
            another: {
              path: {
                'data.json': '{ "fakeData": "yes" }'
              }
            }
          }
        })
      })
      afterEach(() => {
        vol.reset()
      })
      test('myUtility.run() processes test file and creates output', () => {
        // given:
        const myUtility = new MyUtility()
    
        // when: we run the utility
        myUtility.run(`${process.cwd()}/test/UnitTestInput.xlsx`)
    
        // then: output is generated and matches snapshot
        expect(fs.readFileSync(`${process.cwd()}/test/another/path/data-generated.json`, 'utf8')).toMatchSnapshot()
        expect(fs.existsSync(`${process.cwd()}/test/report-UnitTestInput.txt`)).toBeTruthy()
      })
    })
    
  12. sketchbuch commented on Jul 20, 2024

    @sketchbuch

    I was having issues with my Jest tests still using the built-in fs instead of the memfs implementation. This worked to mock out fs & fs/promises:

    import { fs, vol } from 'memfs'
    // import needs to match what you used in your code
    jest.mock('fs', () => fs)
    jest.mock('fs/promises', () => fs.promises)
    
    // test code
    

    What is the mocha equiv. of jest.mock?

  13. BadIdeaException commented on Jul 24, 2024

    @BadIdeaException

    In mocha there is a little more work required, because it does not include a mocking system of its own. Usually you use the excellent Sinon for mocking, but in this special case this won't work since, by specification, imports must be unalterable. Instead, I use esmock to mock out modules. Something like this works for me:

    // Note we DO NOT import the system under test here
    import { vol } from 'memfs';
    
    describe('test', function() {
        let sut;
        before(async function() {
            sut = (await esmock('../path/to/sut.js', {}, {
                'fs': vol,
                'fs/promises': vol.promisesApi
            })).default;
        });
    
        beforeEach(function() {
            vol.fromNestedJSON({ ... });
        });
    
        afterEach(function() {
            vol.reset();
        });
    
        it('should do something with the sut', async function() {
            const result = await sut.doSomething(); // Even works if the sut imported 'fs/promises' like every sane person does
            expect(result).to.be.something.really.nice;
        });
    });
    
  14. TheJaredWilcurt commented on Mar 16, 2026

    @TheJaredWilcurt

    Note for my future self. Mocking writeFileSync with memfs and vitest.

    import { fs, vol } from 'memfs';
    
    /**
     * import { writeFileSync } from 'node:fs';
     *
     * function myWriteFunction (data) {
     *   try {
     *     writeFileSync('./location.txt', data);
     *   } catch {
     *     console.log('Failed');
     *   }
     * }
     */
    import { myWriteFunction } from '@/myFile.js';
    
    vi.mock('node:fs', () => {
      return {
        ...fs,
        writeFileSync: vi.fn((file, data) => {
          if (global.writeFileSyncShouldThrow) {
            throw 'Error message';
          } else {
            return fs.writeFileSync(file, data);
          }
        })
      };
    });
    
    describe('myFile.js', () => {
      const consoleLog = console.log;
    
      beforeEach(() => {
        console.log = vi.fn();
        vi.resetAllMocks();
        vol.reset();
      });
    
      afterEach(() => {
        global.writeFileSyncShouldThrow = false;
        console.log = consoleLog;
      });
    
      test('Writes to the file', () => {
        myWriteFunction('my data');
    
        expect(String(vol.readFileSync('./location.txt')))
          .toEqual('my data');
      });
    
      test('Catches failure to write to file system', () => {
        global.writeFileSyncShouldThrow = true;
    
        myWriteFunction('my data');
    
        expect(console.log)
          .toHaveBeenCalledWith('Failed');
      });
    });

    SEO:
    vitest mock writefilesync memfs
    vitest force writing to filesystem to fail

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions