Simple API to create disposable test fixtures on disk. Tiny (1.1 kB gzipped) with zero dependencies!
- 📁 Create files & directories from simple objects
- 🧹 Automatic cleanup with
usingkeyword - 📝 Built-in JSON read/write support
- 🔗 Symlink support
- 💾 Binary file support with Buffers
- 🎯 TypeScript-first with full type safety
- 🔄 File methods inherit types directly from Node.js
fsmodule - 🔌 Pluggable filesystem — use with @platformatic/vfs, memfs, or any
fs/promises-compatible API
npm install fs-fixtureimport { createFixture } from 'fs-fixture'
// Create a temporary fixture
const fixture = await createFixture({
'package.json': JSON.stringify({ name: 'my-app' }),
'src/index.js': 'console.log("Hello world")'
})
// Read files
const content = await fixture.readFile('src/index.js', 'utf8')
// Cleanup when done
await fixture.rm()Uses TypeScript 5.2+ Explicit Resource Management for automatic cleanup:
await using fixture = await createFixture({
'config.json': '{ "setting": true }'
})
// Fixture is automatically cleaned up when exiting scopeAlready a sponsor? Join the discussion in the Development repo!
From an object:
const fixture = await createFixture({
'package.json': '{ "name": "test" }',
'src/index.js': 'export default () => {}',
'src/utils': {
'helper.js': 'export const help = () => {}'
}
})From a template directory:
// Copies an existing directory structure
const fixture = await createFixture('./test-templates/basic')Empty fixture:
// Create an empty temporary directory
const fixture = await createFixture()File methods (readFile, writeFile, readdir) inherit their type signatures directly from Node.js fs/promises, preserving all overloads and type narrowing behavior.
Read files:
// Read as string (type: Promise<string>)
const text = await fixture.readFile('config.txt', 'utf8')
// Read as buffer (type: Promise<Buffer>)
const binary = await fixture.readFile('image.png')Write files:
await fixture.writeFile('output.txt', 'Hello world')
await fixture.writeFile('data.bin', Buffer.from([0x89, 0x50]))JSON operations:
// Write JSON with formatting
await fixture.writeJson('config.json', { port: 3000 })
// Read and parse JSON with type safety
type Config = { port: number }
const config = await fixture.readJson<Config>('config.json')// Create directories
await fixture.mkdir('nested/folders')
// List directory contents
const files = await fixture.readdir('src')
// Copy files into fixture
await fixture.cp('/path/to/file.txt', 'copied-file.txt')
// Move or rename files
await fixture.mv('old-name.txt', 'new-name.txt')
await fixture.mv('file.txt', 'src/file.txt')
// Check if path exists
if (await fixture.exists('optional-file.txt')) {
// ...
}Dynamic content with functions:
const fixture = await createFixture({
'target.txt': 'original file',
'info.txt': ({ fixturePath }) => `Created at: ${fixturePath}`,
'link.txt': ({ symlink }) => symlink('./target.txt')
})Symlinks:
const fixture = await createFixture({
'index.js': 'import pkg from \'pkg\'',
// Symlink individual file or directory
'node_modules/pkg': ({ symlink }) => symlink(process.cwd()),
// Symlink entire directory (useful for sharing node_modules)
node_modules: ({ symlink }) => symlink(path.resolve('node_modules'))
})Binary files:
const fixture = await createFixture({
'image.png': Buffer.from(imageData),
'generated.bin': () => Buffer.from('dynamic binary content')
})Path syntax:
const fixture = await createFixture({
// Nested object syntax
src: {
utils: {
'helper.js': 'export const help = () => {}'
}
},
// Or path syntax (creates same structure)
'src/utils/helper.js': 'export const help = () => {}'
})Pass any fs/promises-compatible API via the fs option to use a virtual filesystem instead of disk:
import { create, MemoryProvider } from '@platformatic/vfs'
import { createFixture } from 'fs-fixture'
const fs = create(new MemoryProvider()).promises
const fixture = await createFixture({
'package.json': JSON.stringify({ name: 'test' }),
'src/index.js': 'export default 42'
}, { fs })
await fixture.readFile('src/index.js', 'utf8') // 'export default 42'Works with any library that implements the fs/promises API shape, including @platformatic/vfs, the future node:vfs, and memfs.
Note
With a custom fs, files only exist in that fs instance. Use fixture.readFile() or fixture.fs to access them — fixture.path is a virtual path that doesn't exist on the real disk.
Note
Template directory sources (string paths) are not supported with custom filesystems because most virtual fs implementations lack recursive cp. Use a FileTree object instead.
Creates a temporary fixture directory and returns a FsFixture instance.
Parameters:
source(optional): String path to template directory, orFileTreeobject defining the structureoptions.tempDir(optional): Custom temp directory. Defaults toos.tmpdir()options.templateFilter(optional): Filter function when copying from template directoryoptions.fs(optional): Customfs/promises-compatible API for virtual filesystem support
Returns: Promise<FsFixture>
const fixture = await createFixture()
const fixture = await createFixture({ 'file.txt': 'content' })
const fixture = await createFixture('./template-dir')
const fixture = await createFixture({}, { tempDir: './custom-temp' })| Method | Description |
|---|---|
fixture.path |
Absolute path to the fixture directory |
fixture.fs |
The underlying fs/promises API used by the fixture |
getPath(...paths) |
Get absolute path to file/directory in fixture |
exists(path?) |
Check if file/directory exists |
rm(path?) |
Delete file/directory (or entire fixture if no path) |
readFile(path, encoding?) |
Read file as string or Buffer |
writeFile(path, content) |
Write string or Buffer to file |
readJson<T>(path) |
Read and parse JSON file |
writeJson(path, data, space?) |
Write JSON with optional formatting |
readdir(path, options?) |
List directory contents |
mkdir(path) |
Create directory (recursive) |
cp(source, dest?) |
Copy file/directory into fixture |
mv(source, dest) |
Move or rename file/directory |
FileTree
type FileTree = {
[path: string]: string | Buffer | FileTree | ((api: Api) => string | Buffer | Symlink)
}
type Api = {
fixturePath: string // Fixture root path
filePath: string // Current file path
getPath: (...paths: string[]) => string // Get path from fixture root
symlink: (target: string) => Symlink // Create a symlink
}FsPromises
The subset of fs/promises methods that custom filesystem implementations must provide:
type FsPromises = {
// Required
readFile(path: string, options?): Promise<Buffer | string>
writeFile(path: string, data: string | Buffer, options?): Promise<void>
readdir(path: string, options?): Promise<string[] | Dirent[]>
mkdir(path: string, options?): Promise<string | undefined>
rename(oldPath: string, newPath: string): Promise<void>
access(path: string, mode?: number): Promise<void>
// Optional
rm?(path: string, options?): Promise<void>
unlink?(path: string): Promise<void>
rmdir?(path: string): Promise<void>
symlink?(target: string, path: string, type?: string): Promise<void>
cp?(source: string, destination: string, options?): Promise<void>
mkdtemp?(prefix: string): Promise<string>
}If rm is not available, fs-fixture falls back to recursive removal using readdir({ withFileTypes }) + unlink + rmdir. If mkdtemp is not available, fixture paths are generated with a counter.
Lightweight testing library for Node.js


