mrkeyoor.com_
Sun 20 Sept 11:44 UTC
npmTestingupdated 20 Sept 2026

memfs review

memfs 4.68.1 supplies an in-memory implementation of much of Node's fs API, plus independent Volume objects, JSON fixture import and export, streams, promises, watchers, snapshots, and adapters for the browser File System Access API. Tests can inject its fs object into code without touching the host disk. The package intentionally differs from a real filesystem in places: watcher delivery is deterministic, paths are watched semantically, and platform-specific inode and duplicate-event behavior is absent. Version 4.68.1 fixes unlink so it no longer removes an empty directory; 4.68.0 added file birthTime data.

27.1Mdownloads / wk
Verdict

memfs 4.68.1 took 12.8 seconds and 15 MB across 21 packages in our sandbox, with 0 audit findings, while its full browser bundle failed. Install it for injected Node fs unit tests; keep real-disk coverage for watchers, permissions, platform errors, and browser entry points.

We installed it

Lab card: what happened when we installed memfsScreenshot of memfs documentation
Install✓ · 12.8s21 packages on disk · 15 MB
ImportESM import works · require() works · CommonJS package
Browsern/acould not be bundled for the browser (Node-only code, most likely)
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does memfs install cleanly?

Yes. In a fresh container with an empty cache, npm install memfs finished in 13 seconds, leaving 21 packages and 15 MB on disk. npm audit reported no known vulnerabilities.

Can memfs run in a browser?

Not directly: esbuild could not bundle it for the browser in our run, which normally means it depends on Node built-ins. Use it on the server, or find a browser-targeted alternative.

Does memfs work with both ESM and CommonJS?

Yes. Both import 'memfs' and require('memfs') worked in Node 22 in our run. The package is published as CommonJS.

Does memfs include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

memfs or mock-fs: which should you use?

mock-fs: Choose it when existing tests already expect a process-level fs mock and its global patching tradeoff is acceptable. memfs 4.68.1 took 12.8 seconds and 15 MB across 21 packages in our sandbox, with 0 audit findings, while its full browser bundle failed.

When should you not use memfs?

Correctness depends on permissions, ownership, symlinks across mounts, inode identity, disk pressure, or operating-system error codes

API stability3/5The familiar Node fs method names make ordinary reads, writes, streams, and promises easy to carry between releases. memfs also exposes Volume, memfs(), JSON helpers, File System Access adapters, and snapshot utilities. The package has no exports map, so deep imports such as snapshot paths are not protected boundaries. Version 4.68.1 correcting unlink behavior shows that even a standard-looking method can differ at an edge, which limits the score to 3.
Docs4/5The README links separate guides for the Node fs implementation, browser File System Access APIs, adapters in both directions, snapshots, tree printing, generated reference pages, and coverage. Its watcher table is unusually specific about synchronous delivery, recursive options, abort behavior, and intentional divergences. Exact compatibility still falls back to Node's fs documentation and issue reports, so every native edge is not demonstrated in one place.
Maintenance4/5Version 4.68.1 and the repository's latest push both landed on 2026-08-10. The patch prevents unlink from deleting an empty directory, following releases for birthTime and watcher bridges. GitHub reports 50 open issues and pull requests, and the repository is not archived. Current work covers real behavioral gaps rather than dependency churn alone, though the fast linked-package release train makes version pinning sensible.
Ecosystem5/5npm counted 39,967,796 downloads for the week ending 2026-08-25, and GitHub reports 2,089 stars. memfs uses Node's fs vocabulary and belongs to a related set containing unionfs, fs-monkey, linkfs, spyfs, File System Access adapters, and snapshot tools. Code written for an injected fs object often needs no custom fake, while these companion packages cover layering and unavoidable module patching.

Use it if

  • Unit tests can accept an fs-shaped dependency and need fast, isolated file trees with no host-disk cleanup
  • Fixtures are easier to describe as path-to-content JSON and compare again after the code runs
  • Two filesystem states must coexist in one test for migration, copy, or synchronization behavior
  • Node-style streams, promises, file descriptors, or watchers need a controlled fake before a smaller real-filesystem integration suite
Skip it if

Setup reality

We installed memfs 4.68.1 without a cache in a fresh Node 22 Bookworm sandbox. npm finished in 12.8 seconds and left 21 packages using 15 MB. The package itself is 200 KB unpacked, declares 14 direct dependencies and no peers, and carries Apache-2.0. npm audit found 0 known vulnerabilities. Bundled TypeScript declarations were present. The CommonJS package has no exports map; require and ESM import both worked.

No credential or config file is required. Create a new pair with memfs() or Volume when each test needs private state. The exported fs and vol share a singleton, so reset that volume after every case. Passing fs into application code is safer than patching Node's module cache. fs-monkey patches survive vol.reset and must be restored separately.

Version 4.68 watchers do not reproduce real operating-system timing. The Node-style FSWatcher callback runs inside the mutating operation, reports one event per logical change, and never returns a null filename. Delete and recreate keeps the semantic path watched, unlike a POSIX watcher attached to an old inode. Keep at least one test on a real temporary directory for watcher-dependent code.

Our full-package browser bundle failed in esbuild. memfs documents browser File System Access implementations and adapters, but importing the Node-oriented top level is not proof that a frontend bundler can isolate them. Test the exact subpath and browser target. Streams and promises still require normal completion handling, and unionfs can fall through to the real disk when a memory layer misses.

Patterns

Exercise code against the shared volume write-and-read-file

import { fs } from 'memfs'

fs.writeFileSync('/hello.txt', 'Hello')
console.log(fs.readFileSync('/hello.txt', 'utf8'))

The exported fs points at a package singleton. Clear its volume after the test so files do not leak into another case.

Load a readable fixture tree seed-json-tree

import { fs, vol } from 'memfs'

vol.fromJSON({
  './package.json': '{"name":"demo"}',
  './src/index.js': 'export const ready = true',
}, '/workspace')

fs.readFileSync('/workspace/src/index.js', 'utf8')

The base directory resolves every relative fixture key below one root. Absolute keys ignore that base.

Give one test its own filesystem create-isolated-volume

import { memfs } from 'memfs'

const { fs, vol } = memfs({
  '/config.json': '{"mode":"test"}',
})

memfs() returns a paired fs and Volume with no dependence on the exported singleton, which suits parallel tests.

Remove singleton files after a test reset-between-tests

import { vol } from 'memfs'

afterEach(() => {
  vol.reset()
})

reset removes entries from this Volume. It does not undo module patches, external listeners, or state held in a second Volume.

Compare all text files at once assert-final-tree

expect(vol.toJSON()).toEqual({
  '/output/report.txt': 'complete\n',
})

toJSON is convenient for text. Binary bytes and metadata need Buffer reads or the snapshot utilities instead.

Run code written for fs.promises use-promise-api

const { fs } = memfs()

await fs.promises.mkdir('/cache', { recursive: true })
await fs.promises.writeFile('/cache/value.txt', '42')
const value = await fs.promises.readFile('/cache/value.txt', 'utf8')

Await each call as on disk. An in-memory backend still exposes application mistakes caused by missing promise ordering.

Wait for an in-memory stream to finish test-file-streams

import { once } from 'node:events'

const output = fs.createWriteStream('/events.log')
output.write('start\n')
output.end('done\n')
await once(output, 'finish')

const input = fs.createReadStream('/events.log')

The bytes stay in memory, but write completion remains event based. Read only after finish when order matters.

Compare two isolated file trees use-separate-volumes

import { Volume } from 'memfs'

const first = Volume.fromJSON({ '/value': 'one' })
const second = Volume.fromJSON({ '/value': 'two' })

first.readFileSync('/value', 'utf8')
second.readFileSync('/value', 'utf8')

Each Volume holds its own inode tree. This is useful for migration tests without resetting shared state between phases.

Make the filesystem an explicit parameter inject-fs-dependency

function loadSettings(fsImpl, path) {
  return JSON.parse(fsImpl.readFileSync(path, 'utf8'))
}

const { fs } = memfs({ '/settings.json': '{"port":3000}' })
const settings = loadSettings(fs, '/settings.json')

Dependency injection limits the fake to this call and leaves the process-wide node:fs module untouched.

Overlay memory on the host filesystem combine-memory-and-disk

import * as realFs from 'node:fs'
import { ufs } from 'unionfs'

ufs.use(realFs).use(vol)
const data = ufs.readFileSync('/virtual.txt', 'utf8')

Layer order controls which duplicate path wins. A miss can reach realFs, so this arrangement is not a security sandbox.

Close a recursive watcher with a signal watch-with-abort

const controller = new AbortController()
const watcher = fs.watch('/workspace', {
  recursive: true,
  signal: controller.signal,
}, (eventType, filename) => {
  console.log(eventType, filename)
})

controller.abort()

memfs calls this watcher synchronously during mutation and emits deterministic events. Native Node watchers have different timing and platform behavior.

Restore a complete directory state snapshot-directory

import * as snapshot from 'memfs/lib/snapshot'

const saved = snapshot.toSnapshotSync({ fs, path: '/workspace' })
vol.reset()
snapshot.fromSnapshotSync(saved, { fs, path: '/workspace' })

This snapshot utility is reached through a deep import in a package with no exports map. Pin and test that path during upgrades.

Alternatives

PackageRegistryPick it when
mock-fsnpmChoose it when existing tests already expect a process-level fs mock and its global patching tradeoff is acceptable.
unionfsnpmChoose it when reads must combine an in-memory volume with another filesystem layer.
fs-monkeynpmChoose it only when code cannot accept an injected fs object and Node module patching is unavoidable.

More testing guides

pytest · chai · vitest · jsdom · playwright · coverage · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.