How to mock fs module together with unionfs?

468 views Asked by At

I have written a test case that successfully load files into virtual FS, and at the same time mounted a virtual volume as below

describe("should work", () => {
  const { vol } = require("memfs");
  afterEach(() => vol.reset());
  beforeEach(() => {
    vol.mkdirSync(process.cwd(), { recursive: true });
    jest.resetModules();
    jest.resetAllMocks();
  });

it("should be able to mock fs that being called in actual code", async () => {
    jest.mock("fs", () => {
      return ufs //
        .use(jest.requireActual("fs"))
        .use(createFsFromVolume(vol) as any);
    });
    jest.mock("fs/promises", () => {
      return ufs //
        .use(jest.requireActual("fs/promises"))
        .use(createFsFromVolume(vol) as any);
    });
    const { createFsFromVolume } = require("memfs");
    const { ufs } = require("unionfs");
    const { countFile } = require("../src/ops/fs");

    vol.fromJSON(
      {
        "./some/README.md": "1",
        "./some/index.js": "2",
        "./destination": null,
      },
      "/app"
    );

    const result = ufs.readdirSync(process.cwd());
    const result2 = ufs.readdirSync("/app");
    const result3 = await countFile("/app");

    console.log({ result, result2, result3 });
  });
});

By using ufs.readdirSync, I can access to virtual FS and indeed result giving me files that loaded from disc into virtual FS, result2 representing /app which is a new volume created from vol.fromJSON.

Now my problem is I am unable to get the result for result3, which is calling countFile method as below

import fsPromises from "fs/promises";

export const countFile = async (path: string) => {
  const result = await fsPromises.readdir(path);
  return result.length;
};

I'm getting error

Error: ENOENT: no such file or directory, scandir '/app'

which I think it's because countFile is accessing the actual FS instead of the virtual despite I've had jest.mock('fs/promises')?

Please if anyone can provide some lead?

1

There are 1 answers

0
Tony On

This is the function you want to unit test.

//CommonJS version
const fsPromises = require('fs/promises');

const countFile = async (path) => {
  const result = await fsPromises.readdir(path);
  return result.length;
};

module.exports = {
    countFile
}

Now, how you would normally go about this, is to mock fsPromises. In this example specifically readdir() since that is the function being used in countFile.

This is what we call: a stub.

A skeletal or special-purpose implementation of a software component, used to develop or test a component that calls or is otherwise dependent on it. It replaces a called component.

const {countFile} = require('./index');
const {readdir} = require("fs/promises");

jest.mock('fs/promises');

beforeEach(() => {
    readdir.mockReset();
});

it("When testing countFile, given string, then return files", async () => {
    const path = "/path/to/dir";
// vvvvvvv STUB HERE
    readdir.mockResolvedValueOnce(["src", "node_modules", "package-lock.json" ,"package.json"]);
    const res = await countFile(path);
    expect(res).toBe(4);
})

You do this because you're unit testing. You don't want to be dependent on other functions because that fails to be a unit test and more integration test. Secondly, it's a third-party library, which is maintained/tested by someone else.

Here is where your scenario applies. From my perspective, your objective isn't to test countFile() rather, to test fsPromises and maybe test functionality to read virtual file-systems: unionfs. If so then, fsPromises doesn't need to really be mocked.