mock-fs vs fs-extra vs memfs vs memory-fs
File System Abstraction and Testing Utilities for Node.js
mock-fsfs-extramemfsmemory-fsSimilar Packages:

File System Abstraction and Testing Utilities for Node.js

fs-extra extends the built-in Node.js fs module with additional methods like ensureDir and promise support. memfs and memory-fs implement a virtual file system in memory, useful for testing or isolated environments. mock-fs intercepts calls to the real fs module to simulate files and directories without touching the disk. Together, these tools cover production file operations, in-memory isolation, and test mocking scenarios.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
mock-fs1,016,000920107 kB74a year agoMIT
fs-extra09,61558.1 kB13a day agoMIT
memfs02,05929.4 kB5321 days agoApache-2.0
memory-fs0879-307 years agoMIT

File System Utilities for Node.js Development and Testing

When building Node.js applications, handling files safely and efficiently is critical. The packages fs-extra, memfs, memory-fs, and mock-fs all address file system interactions, but they serve different purposes. Some enhance the real file system, while others simulate it entirely. Let's compare how they handle common tasks.

๐Ÿ“ Basic File Operations: Reading and Writing

fs-extra works directly with the real disk but adds convenience methods.

const fs = require('fs-extra');

// Reads from actual disk
const data = await fs.readFile('/path/to/file.txt', 'utf8');

memfs creates a virtual file system instance that lives in RAM.

const { fs } = require('memfs');

// Reads from memory volume
const data = fs.readFileSync('/path/to/file.txt', 'utf8');

memory-fs also uses RAM but is legacy technology.

const MemoryFileSystem = require('memory-fs');
const fs = new MemoryFileSystem();

// Reads from legacy memory volume
const data = fs.readFileSync('/path/to/file.txt', 'utf8');

mock-fs tricks the native fs module into reading from a fake structure.

const mock = require('mock-fs');
const fs = require('fs');

mock({ '/path/to/file.txt': 'content' });

// Reads from mocked structure
const data = fs.readFileSync('/path/to/file.txt', 'utf8');

๐Ÿ“ Directory Management: Creating Folders Safely

Creating directories often fails if parent folders do not exist. These packages handle that differently.

fs-extra provides ensureDir, which creates all missing parents automatically.

const fs = require('fs-extra');

// Creates /tmp/new/dir even if /tmp/new is missing
await fs.ensureDir('/tmp/new/dir');

memfs relies on the standard recursive option available in modern Node.js.

const { fs } = require('memfs');

// Creates directory tree in memory
fs.mkdirSync('/tmp/new/dir', { recursive: true });

memory-fs uses a specific helper method mkdirp.

const MemoryFileSystem = require('memory-fs');
const fs = new MemoryFileSystem();

// Creates directory tree in legacy memory
fs.mkdirpSync('/tmp/new/dir');

mock-fs defines the entire structure upfront in the mock config.

const mock = require('mock-fs');

// Define structure before test runs
mock({
  '/tmp/new/dir': {}
});

๐Ÿงช Testing Strategy: Isolation and Cleanup

When writing tests, you want to avoid leaving files on your computer after the test runs.

fs-extra requires manual cleanup since it touches the real disk.

const fs = require('fs-extra');

await fs.writeFile('/tmp/test.txt', 'data');
// You must manually delete this later
await fs.remove('/tmp/test.txt');

memfs isolates data in memory, so cleanup is instant when the process ends.

const { fs, vol } = require('memfs');

vol.fromJSON({ '/test.txt': 'data' });
// No disk cleanup needed

memory-fs also isolates in memory but lacks modern features.

const MemoryFileSystem = require('memory-fs');
const fs = new MemoryFileSystem();

fs.writeFileSync('/test.txt', 'data');
// Isolated but deprecated

mock-fs swaps the real module and restores it after tests.

const mock = require('mock-fs');

mock({ '/test.txt': 'data' });
// Run tests
mock.restore(); // Returns to real fs

โš ๏ธ Maintenance Status: Active vs Deprecated

This is the most critical factor for long-term projects.

fs-extra is actively maintained and stable.

// Safe for production use
const fs = require('fs-extra');

memfs is actively maintained and recommended for in-memory needs.

// Safe for modern projects
const { fs } = require('memfs');

memory-fs is deprecated and should not be used.

// DO NOT USE - Deprecated
const MemoryFileSystem = require('memory-fs');

mock-fs is maintained for testing purposes.

// Safe for testing
const mock = require('mock-fs');

๐Ÿ“Š Summary: Key Differences

Featurefs-extramemfsmemory-fsmock-fs
Storage๐Ÿ–ฅ๏ธ Real Disk๐Ÿ’พ Memory๐Ÿ’พ Memory๐ŸŽญ Mocked Disk
Statusโœ… Activeโœ… ActiveโŒ Deprecatedโœ… Active
Best ForProduction IOVirtual FSLegacy CodeUnit Tests
SetupDrop-in ReplaceNew InstanceNew InstanceGlobal Swap
CleanupManualAuto (RAM)Auto (RAM)Manual Restore

๐Ÿ’ก The Big Picture

fs-extra is the standard choice for real file operations โ€” it makes working with the actual disk safer and easier. Use it for build scripts, server uploads, or any task that needs persistent storage.

memfs is the modern standard for in-memory file systems โ€” it is fast and isolated. Use it for testing tools, bundlers, or scenarios where disk I/O is too slow or undesirable.

mock-fs is the specialist for testing โ€” it lets you fake the file system without changing your code. Use it when you need to test how your app handles missing files or specific directory structures.

memory-fs should be avoided โ€” it is deprecated and replaced by memfs. Only interact with it if you are maintaining older webpack configurations.

Final Thought: For new projects, combine fs-extra for real disk tasks and memfs or mock-fs for testing. Avoid memory-fs entirely to ensure your stack remains supported.

How to Choose: mock-fs vs fs-extra vs memfs vs memory-fs

  • mock-fs:

    Choose mock-fs when you need to mock the native fs module during testing without changing your application code. It allows you to define a fake file structure that your tests can interact with safely. This is best for unit tests that require file system isolation.

  • fs-extra:

    Choose fs-extra for production applications that need reliable file operations beyond the standard library. It is ideal when you require methods like ensureDir or move that simplify common tasks. This package is stable and widely adopted for server-side Node.js development.

  • memfs:

    Choose memfs when you need a modern, actively maintained in-memory file system implementation. It is suitable for testing environments or tools that require fast, isolated file operations without disk I/O. This package is the recommended successor to older in-memory solutions.

  • memory-fs:

    Do not choose memory-fs for new projects as it is officially deprecated and no longer maintained. It was previously used by webpack but has been replaced by memfs. You should only encounter this in legacy codebases that have not yet migrated.

README for mock-fs

Build Status

mock-fs

The mock-fs module allows Node's built-in fs module to be backed temporarily by an in-memory, mock file system. This lets you run tests against a set of mock files and directories instead of lugging around a bunch of test fixtures.

Example

The code below makes it so the fs module is temporarily backed by a mock file system with a few files and directories.

const mock = require('mock-fs');

mock({
  'path/to/fake/dir': {
    'some-file.txt': 'file content here',
    'empty-dir': {/** empty directory */}
  },
  'path/to/some.png': Buffer.from([8, 6, 7, 5, 3, 0, 9]),
  'some/other/path': {/** another empty directory */}
});

When you are ready to restore the fs module (so that it is backed by your real file system), call mock.restore(). Note that calling this may be mandatory in some cases. See istanbuljs/nyc#324

// after a test runs
mock.restore();

Upgrading to version 4

Instead of overriding all methods of the built-in fs module, the library now overrides process.binding('fs'). The purpose of this change is to avoid conflicts with other libraries that override fs methods (e.g. graceful-fs) and to make it possible to work with multiple Node releases without maintaining copied and slightly modified versions of Node's fs module.

Breaking changes:

  • The mock.fs() function has been removed. This returned an object with fs-like methods without overriding the built-in fs module.
  • The object created by fs.Stats is no longer an instance of fs.Stats (though it has all the same properties and methods).
  • Lazy require() do not use the real filesystem.
  • Tests are no longer run in Node < 4.

Some of these breaking changes may be restored in a future release.

Docs

mock(config, options)

Configure the fs module so it is backed by an in-memory file system.

Calling mock sets up a mock file system with two directories by default: process.cwd() and os.tmpdir() (or os.tmpDir() for older Node). When called with no arguments, just these two directories are created. When called with a config object, additional files, directories, and symlinks are created. To avoid creating a directory for process.cwd() and os.tmpdir(), see the options below.

Property names of the config object are interpreted as relative paths to resources (relative from process.cwd()). Property values of the config object are interpreted as content or configuration for the generated resources.

Note that paths should always use forward slashes (/) - even on Windows.

options

The second (optional) argument may include the properties below.

  • createCwd - boolean Create a directory for process.cwd(). This is true by default.
  • createTmp - boolean Create a directory for os.tmpdir(). This is true by default.

Loading real files & directories

You can load real files and directories into the mock system using mock.load()

Notes

  • All stat information is duplicated (dates, permissions, etc)
  • By default, all files are lazy-loaded, unless you specify the {lazy: false} option

options

OptionTypeDefaultDescription
lazybooleantrueFile content isn't loaded until explicitly read
recursivebooleantrueLoad all files and directories recursively

mock.load(path, options)

mock({
  // Lazy-load file
  'my-file.txt': mock.load(path.resolve(__dirname, 'assets/special-file.txt')),
  
  // Pre-load js file
  'ready.js': mock.load(path.resolve(__dirname, 'scripts/ready.js'), {lazy: false}),

  // Recursively loads all node_modules
  'node_modules': mock.load(path.resolve(__dirname, '../node_modules')),

  // Creates a directory named /tmp with only the files in /tmp/special_tmp_files (no subdirectories), pre-loading all content
  '/tmp': mock.load('/tmp/special_tmp_files', {recursive: false, lazy:false}),

  'fakefile.txt': 'content here'
});

Creating files

When config property values are a string or Buffer, a file is created with the provided content. For example, the following configuration creates a single file with string content (in addition to the two default directories).

mock({
  'path/to/file.txt': 'file content here'
});

To create a file with additional properties (owner, permissions, atime, etc.), use the mock.file() function described below.

mock.file(properties)

Create a factory for new files. Supported properties:

  • content - string|Buffer File contents.
  • mode - number File mode (permission and sticky bits). Defaults to 0666.
  • uid - number The user id. Defaults to process.getuid().
  • gid - number The group id. Defaults to process.getgid().
  • atime - Date The last file access time. Defaults to new Date(). Updated when file contents are accessed.
  • ctime - Date The last file change time. Defaults to new Date(). Updated when file owner or permissions change.
  • mtime - Date The last file modification time. Defaults to new Date(). Updated when file contents change.
  • birthtime - Date The time of file creation. Defaults to new Date().

To create a mock filesystem with a very old file named foo, you could do something like this:

mock({
  foo: mock.file({
    content: 'file content here',
    ctime: new Date(1),
    mtime: new Date(1)
  })
});

Note that if you want to create a file with the default properties, you can provide a string or Buffer directly instead of calling mock.file().

Creating directories

When config property values are an Object, a directory is created. The structure of the object is the same as the config object itself. So an empty directory can be created with a simple object literal ({}). The following configuration creates a directory containing two files (in addition to the two default directories):

// note that this could also be written as
// mock({'path/to/dir': { /** config */ }})
mock({
  path: {
    to: {
      dir: {
        file1: 'text content',
        file2: Buffer.from([1, 2, 3, 4])
      }
    }
  }
});

To create a directory with additional properties (owner, permissions, atime, etc.), use the mock.directory() function described below.

mock.directory(properties)

Create a factory for new directories. Supported properties:

  • mode - number Directory mode (permission and sticky bits). Defaults to 0777.
  • uid - number The user id. Defaults to process.getuid().
  • gid - number The group id. Defaults to process.getgid().
  • atime - Date The last directory access time. Defaults to new Date().
  • ctime - Date The last directory change time. Defaults to new Date(). Updated when owner or permissions change.
  • mtime - Date The last directory modification time. Defaults to new Date(). Updated when an item is added, removed, or renamed.
  • birthtime - Date The time of directory creation. Defaults to new Date().
  • items - Object Directory contents. Members will generate additional files, directories, or symlinks.

To create a mock filesystem with a directory with the relative path some/dir that has a mode of 0755 and two child files, you could do something like this:

mock({
  'some/dir': mock.directory({
    mode: 0755,
    items: {
      file1: 'file one content',
      file2: Buffer.from([8, 6, 7, 5, 3, 0, 9])
    }
  })
});

Note that if you want to create a directory with the default properties, you can provide an Object directly instead of calling mock.directory().

Creating symlinks

Using a string or a Buffer is a shortcut for creating files with default properties. Using an Object is a shortcut for creating a directory with default properties. There is no shortcut for creating symlinks. To create a symlink, you need to call the mock.symlink() function described below.

mock.symlink(properties)

Create a factory for new symlinks. Supported properties:

  • path - string Path to the source (required).
  • mode - number Symlink mode (permission and sticky bits). Defaults to 0666.
  • uid - number The user id. Defaults to process.getuid().
  • gid - number The group id. Defaults to process.getgid().
  • atime - Date The last symlink access time. Defaults to new Date().
  • ctime - Date The last symlink change time. Defaults to new Date().
  • mtime - Date The last symlink modification time. Defaults to new Date().
  • birthtime - Date The time of symlink creation. Defaults to new Date().

To create a mock filesystem with a file and a symlink, you could do something like this:

mock({
  'some/dir': {
    'regular-file': 'file contents',
    'a-symlink': mock.symlink({
      path: 'regular-file'
    })
  }
});

Restoring the file system

mock.restore()

Restore the fs binding to the real file system. This undoes the effect of calling mock(). Typically, you would set up a mock file system before running a test and restore the original after. Using a test runner with beforeEach and afterEach hooks, this might look like the following:

beforeEach(function() {
  mock({
    'fake-file': 'file contents'
  });
});
afterEach(mock.restore);

Bypassing the mock file system

mock.bypass(fn)

Execute calls to the real filesystem with mock.bypass()

// This file exists only on the real FS, not on the mocked FS
const realFilePath = '/path/to/real/file.txt';
const myData = mock.bypass(() => fs.readFileSync(realFilePath, 'utf-8'));

If you pass an asynchronous function or a promise-returning function to bypass(), a promise will be returned.

Async Warning

Asynchronous calls are supported, however, they are not recommended as they could produce unintended consequences if anything else tries to access the mocked filesystem before they've completed.

async function getFileInfo(fileName) {
  return await mock.bypass(async () => {
    const stats = await fs.promises.stat(fileName);
    const data = await fs.promises.readFile(fileName);
    return {stats, data};
  });
}

Install

Using npm:

npm install mock-fs --save-dev

Caveats

When you require mock-fs, Node's own fs module is patched to allow the binding to the underlying file system to be swapped out. If you require mock-fs before any other modules that modify fs (e.g. graceful-fs), the mock should behave as expected.

Note mock-fs is not compatible with graceful-fs@3.x but works with graceful-fs@4.x.

Mock file access is controlled based on file mode where process.getuid() and process.getgid() are available (POSIX systems). On other systems (e.g. Windows) the file mode has no effect.

Tested on Linux, OSX, and Windows using Node 18 through 22. Check the tickets for a list of known issues.

Using with Jest Snapshot Testing

.toMatchSnapshot in Jest uses fs to load existing snapshots. If mockFs is active, Jest isn't able to load existing snapshots. In such case it accepts all snapshots without diffing the old ones, which breaks the concept of snapshot testing.

Calling mock.restore() in afterEach is too late and it's necessary to call it before snapshot matching:

const actual = testedFunction()
mock.restore()
expect(actual).toMatchSnapshot()

Note: it's safe to call mock.restore multiple times, so it can still be called in afterEach and then manually in test cases which use snapshot testing.