Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix compatibility with Node 20 #384

Closed
ademdinarevic opened this issue Dec 21, 2023 · 13 comments · Fixed by #387
Closed

Fix compatibility with Node 20 #384

ademdinarevic opened this issue Dec 21, 2023 · 13 comments · Fixed by #387

Comments

@ademdinarevic
Copy link

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",
  },
});
@zbigg
Copy link

zbigg commented Dec 22, 2023

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');
};

@canhassancode
Copy link

canhassancode commented Dec 22, 2023

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'

@crfrolik
Copy link

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.

@PedroS11
Copy link

PedroS11 commented Feb 6, 2024

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

@JBloss1517
Copy link

@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!

@PedroS11
Copy link

PedroS11 commented Feb 24, 2024

@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

polomsky added a commit to SuitestAutomation/suitest-js-api that referenced this issue Mar 6, 2024
Currently, can not use Node.js >= 20.10 because mock-fs used in the unit
tests does not support it. tschaub/mock-fs#384
polomsky added a commit to SuitestAutomation/suitest-js-api that referenced this issue Mar 6, 2024
Currently, can not use Node.js >= 20.9 because mock-fs used in the unit
tests does not support it. tschaub/mock-fs#384
polomsky added a commit to SuitestAutomation/suitest-js-api that referenced this issue Mar 6, 2024
Currently, can not use Node.js >= 20.8 because mock-fs used in the unit
tests does not support it. tschaub/mock-fs#384
polomsky added a commit to SuitestAutomation/suitest-js-api that referenced this issue Mar 6, 2024
Currently, can not use Node.js >= 20.8 because mock-fs used in the unit
tests does not support it. tschaub/mock-fs#384
polomsky added a commit to SuitestAutomation/suitest-js-api that referenced this issue Mar 6, 2024
Currently, can not use Node.js >= 20.8 because mock-fs used in the unit
tests does not support it. tschaub/mock-fs#384
@dennismeister93
Copy link

dennismeister93 commented Mar 10, 2024

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

@tschaub
Copy link
Owner

tschaub commented Mar 18, 2024

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

@dicolasi
Copy link

struggling with the same problem here.

@MichaelSitter
Copy link

MichaelSitter commented May 7, 2024

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

@bcass
Copy link

bcass commented Jun 17, 2024

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()
  })
})

@sketchbuch
Copy link

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?

@BadIdeaException
Copy link

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;
    });
});

BadIdeaException added a commit to quartersbrief/quartersbrief that referenced this issue Jul 24, 2024
mock-fs is incompatible with newer versions of Node (see tschaub/mock-fs#384) and in the long run unlikely to be fixable (tschaub/mock-fs#383).
PaulREnglish added a commit to bigbite/build-tools that referenced this issue Jul 29, 2024
Previously, the jest tests used mock-fs to mock the file system.  However, there is compatibility issues between mock-fs and node 20.  See this issue: tschaub/mock-fs#384
ndelangen added a commit to storybookjs/storybook that referenced this issue Aug 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.