DEV Community

loading...
Cover image for Unit testing Node.js fs with mock-fs

Unit testing Node.js fs with mock-fs

emma profile image Emma Goto πŸ™ Originally published at emgoto.com on ・3 min read

If you're using the fs module to do things like write to files, or modify file names, you might have wondered - how do I unit test this?

In this post I'll be showing you how you can use mock-fs to easily unit test your Node.js scripts.

If you want to learn more about Node.js, check out my posts on automating file renaming with Node.js and writing to files with Node.js.

Set up your Node.js script to be tested

To begin with, we'll be using an example Node.js script that uses fs to replace the string "Hello" with "Goodbye".

This example is fully synchronous, and only uses fs readFileSync and writeFileSync:

const { readFileSync, writeFileSync } = require('fs');

const modifyFile = () => {
    const file = `${process.cwd()}/folderName/index.md`

    const content = readFileSync(file, 'utf8'); // highlight-line
    const newContent = content.replace('Hello', 'Goodbye');

    writeFileSync(file, newContent); // highlight-line
};
Enter fullscreen mode Exit fullscreen mode

If your script is fully synchronous, you'll have no problems and you can keep scrolling down to the mock-fs part below.

However if you're using async functions like fs readFile or writeFile, you'll need to make sure that your script has finished before beginning the unit tests.

We can do this using the fs Promises API.

Using the fs Promises API

Instead of using readFile, use promises.readFile, and you'll be returning a Promise:

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

const modifyFile = async () => {
    const file = `${process.cwd()}/folderName/index.md`

    return promises.readFile(file, 'utf8').then(content => {  // highlight-line
        const newContent = content.replace('Hello', 'Goodbye')
        return promises.writeFile(file, newContent);  // highlight-line
    });
};
Enter fullscreen mode Exit fullscreen mode

This means that in your unit test, you can now use await and make sure your script has completed before testing it:

test('should replace Hello with Goodbye', async () => {
    await modifyFile();
    // ...
Enter fullscreen mode Exit fullscreen mode

Before we make any assertions though, we’ll also need to add some mocks.

Mock your files and folders using mock-fs

We want to be able to mock out some files, because otherwise you would need to have dummy test files that live in your test folder, and you would also need to reset them to their original state at the end of each unit test.

With mock-fs, we can mock out folder structures and the content of files.

Make sure you have it installed first:

npm i mock-fs -D 
# or
yarn add mock-fs -D
Enter fullscreen mode Exit fullscreen mode

Then, add it to the beforeAll hook in your test:

import mock from 'mock-fs';
import { main } from './modifyFile';

describe('modifyFile script', () => {
    beforeAll(() => {
        mock({
            'folderName': {
                'index.md': '# Hello world!',
            },
        });
    });

    afterAll(() => {
        mock.restore();
    });
Enter fullscreen mode Exit fullscreen mode

These folder names are relative to the root of your repository. Here we’re mocking a folder/file structure like this:

folderName
    index.md // <- contains "# Hello world!"
Enter fullscreen mode Exit fullscreen mode

Write a unit test on file modification with mock-fs

Now we can continue with our unit test. We can assert on the file's contents:

test('should replace hello with goodbye', async () => {
    const file = `${process.cwd()}/folderName/index.md`
    const expectedResult = `# Goodbye world`;

    await modifyFile();

    const result = readFileSync(file, 'utf8');
    expect(result).toEqual(expectedResult);
});
Enter fullscreen mode Exit fullscreen mode

When we call modifyFile, we'll be modifying the mocked file. We can then confirm that the file was successfully modified by using readFileSync to read it.

Write a unit test on file renaming with mock-fs

In the case where we want to unit test that files were renamed, we can do the following:

import glob from 'glob';

test('should successfully move and rename files', async () => {
    const expectedFiles = [
        `${process.cwd()}/folderName/renamedFile.md`,
    ];

    await modifyFile();

    const files = glob.sync(`${process.cwd()}/folderName/*.md`);

    expect(files).toEqual(expectedFiles);
});
Enter fullscreen mode Exit fullscreen mode

Since we have used mock-fs, your script can also rename mocked files. Then we can use glob to verify that our files were renamed as expected.

Discussion (0)

pic
Editor guide