Back to posts

On mocking fs with metro-memory-fs

January 2, 2020

If you've done your share of testing Node, you've probably found yourself wanting to mock the file-system, particularly the fs core module. I've had mixed success with this historically; generally it becomes an exercise in GitHub Issue and StackOverflow spelunking.

In my last foray, I stumbled upon this issue. After reading through it I found that someone suggested the in-memory fs used by the React Native bundler Metro for its automated tests. As luck would have it, that module is already published as metro-memory-fs. Sweet!

Getting Started With metro-memory-fs

There's not much documentation for metro-memory-fs (read: none 😅), but fortunately it's pretty straightforward! I'm going to assume that you are using jest to write your unit tests from here on out.

We can start by installing it:

$ yarn add --dev metro-memory-fs
 
# or if you use npm
 
$ npm install --save-dev metro-memory-fs

In your tests, you can initialize the mock like this:

jest.mock("fs", () => new (require("metro-memory-fs"))());
 
// if you want to reset the mock before each test case:
beforeEach(() => require("fs").reset());

Now that we're setup we can start acting against our mocked fs instance.

Note: cwd configuration

If you encounter an error similar to this:

The path /some/path/to/file.txt cannot be resolved because no
current working directory function has been specified. Set the
`cwd` option field to specify a current working directory.

Then you might have to configure the cwd option like this:

jest.mock("fs", () => new (require("metro-memory-fs"))({ cwd: () => "/" }));

This was enough for me to fix the issue and continue writing my tests.

Writing Tests

Let's assume that we've got a function which writes to a file called test.txt. Problem is, we don't want to have to manually delete the file after our tests are done. Which is probably why we stumbled on this article... 🤔. Now that we've got our mock setup with metro-memory-fs this should be easy!

Here's our test setup:

import fs from "fs";
import writeTestFile from "../write-test-file";
 
jest.mock("fs", () => new (require("metro-memory-fs"))({ cwd: () => "/" }));
 
beforeEach(() => require("fs").reset());
 
describe("writeTestFile", () => {});

Alright, looking good. Let's add a test case...

import fs from "fs";
import writeTestFile from "../write-test-file";
 
jest.mock("fs", () => new (require("metro-memory-fs"))({ cwd: () => "/" }));
 
beforeEach(() => require("fs").reset());
 
describe("writeTestFile", () => {
  test("writes to test.txt", async () => {
    // invoke our function, writing to /test.txt
    await writeTestFile("/");
 
    // read our written file to validate its contents
    const result = await fs.promises.readFile("test.txt");
    expect(result).toEqual("test");
  });
});

That's it! By mocking fs with metro-memory-fs we are able to write tests against the file-system with minimal ceremony. 🎉

Reducing Boilerplate

If you anticipate having to lean on metro-memory-fs for many of your tests you could specify the mock in one of your setupFiles, or create an importable helper like this:

// mock-fs.js
jest.mock("fs", () => new (require("metro-memory-fs"))({ cwd: () => "/" }));
beforeEach(() => require("fs").reset());
 
// in a test file:
import "./utils/mock-fs";

That's A Wrap

Mocking fs doesn't have to be hard, I'm pretty glad I stumbled upon metro-memory-fs. Hopefully this post can serve as simple documentation for the package and save you a few minutes if you decide to implement metro-memory-fs in your own project! I'll do my best to update this post where necessary as I continue to take advantage of the package in my own test suites.

Credits

Back to posts