fs-syncer
A helper to recursively read and write text files to a specified directory.
The idea
It's a pain to write tests for tools that interact with the filesystem. It would be useful to write assertions that look something like:
expect(someDirectory.read()).toEqual({
'file1.txt': 'some info',
'file2.log': 'something logged',
nested: {
sub: {
directory: {
'deeply-nested-file.sql': 'SELECT * FROM abc'
},
},
},
})
Similarly, as part of test setup, you might want to write several files, e.g.:
write({
migrations: {
'migration1.sql': 'create table one(id text)',
'migration2.sql': 'create table two(id text)',
down: {
'migration1.sql': 'drop table one',
'migration2.sql': 'drop table two',
},
},
})
The problem is that usually, you have to write a recursive directory-walker function, an object-to-filepath converter function, a nested-object-getter-function and a few more functions that tie them all together.
Then, if you have the energy, you should also write a function that cleans up any extraneous files after tests have been run. Or, you can pull in several dependencies that do some of these things for you, then write some functions that tie them together.
Now, you can just use fs-syncer
, which does all of the above. Here's the API:
import {fsSyncer} from 'fs-syncer'
const syncer = fsSyncer(__dirname + '/migrations', {
'migration1.sql': 'create table one(id text)',
'migration2.sql': 'create table two(id text)',
down: {
'migration1.sql': 'drop table one',
'migration2.sql': 'drop table two',
},
})
syncer.sync()
syncer.read()
require('fs').writeFileSync(__dirname + '/migrations/extraneous.txt', 'abc', 'utf8')
syncer.read()
syncer.sync()
syncer.write()
Usage with vitest or jest
⚠️⚠️⚠️ This feature is new and experimental - if you try it out, be aware that the API is in flux. Feedback is welcome! ⚠️⚠️⚠️
If you happen to want to use this in a jest test, there's an opinionated helper which allows you to avoid supplying a baseDir
parameter.
Let's say you want to test a file modification tool, which appends // comments
to all the files it finds under a certain directory, and also creates a log file:
import {testFixture} from 'fs-syncer'
import {fileModificationToolThatYouWantToTest} from '../src/your-library'
test('files are modified', async () => {
const fixture = testFixture({
expect,
targetState: {
'file1.txt': 'hello I am a file',
nested: {
'file2.txt': 'I am also a file',
}
}
})
fixture.sync()
await fileModificationToolThatYouWantToTest.run({
directory: fixture.baseDir,
logFile: 'abc.log',
})
expect(fixture.yaml()).toMatchInlineSnapshot()
})
fixture.yaml()
is a helper that returns a yaml string representing the file tree. It's intended to be human-readable and familiar, and should not be relied on to be valid yaml, it's mostly for test snapshots.
Let's assume the test file containing this test is called my-test-file.test.ts
. When run, the above test will generate a directory fixtures/my-test-file.test.ts/files-are-modified
next to the test file. The directory structure described in targetState
will be created inside that folder. The test above might end up looking something like when run:
import {testFixture} from 'fs-syncer'
import {fileModificationToolThatYouWantToTest} from '../src/your-library'
test('files are modified', async () => {
const fixture = testFixture({
expect,
targetState: {
'file1.txt': 'hello I am a file',
nested: {
'file2.txt': 'I am also a file',
}
}
})
fixture.sync()
await fileModificationToolThatYouWantToTest.run({
directory: fixture.baseDir,
logFile: 'abc.log',
})
expect(fixture.yaml()).toMatchInlineSnapshot(
`"---
abc.log: |-
added content to file1.txt
added content to nested/file2.txt
file1.txt: |-
hello I am a file
// this content was auto-generated by the tool
nested:
file2.txt: |-
hello I am a file
// this content was auto-generated by the tool
"`
)
})
Not supported (right now)
- File content other than text, e.g.
Buffer
s. The library assumes you are solely dealing with utf8 strings. - Any performance optimisations - you will probably have a bad time if you try to use it to read or write a very large number of files.
- Any custom symlink behaviour.
Comparison with mock-fs
This isn't a mocking library. There's no magic under the hood, it just calls fs.readFileSync
, fs.writeFileSync
and fs.mkdirSync
directly. Which means you can use it anywhere - it could even be a runtime dependency as a wrapper for the fs
module. And using it doesn't have any weird side-effects like breaking jest snapshot testing. Not being a mocking library means you could use it in combination with mock-fs, if you really wanted.