engineering

Unit testing and mocking fs.ReadFileSync.

In this article, you’ll learn how to isolate filesystem access in Jest tests without turning mocks into an inaccurate model of Node.js. That matters because durable engineering comes from understanding trade-offs, not merely reproducing a command or pattern.

I’d just ran npm run test in a newly created package I’d added to a monorepo (lerna) I’d created for a project I was working on that integrates with Twilio Sync, RabbitMQ, Twilio TaskRouter and MSSQL, and I go this:

*******************************consumers\packages\eda [CRMBROK-233 +0 ~2 -0 !]> npm run test

> @cf247/eda@1.0.2 test *******************************consumers\packages\eda
> jest

 FAIL  __tests__/eda.test.js
   Test suite failed to run

    ENOENT: no such file or directory, open '.env'

      2 | const fs = require('fs')
      3 | const dotenv = require('dotenv')
    > 4 | const envConfig = dotenv.parse(fs.readFileSync(`.env`))
        |                                   ^
      5 | for (const k in envConfig) {
      6 |     process.env[k] = envConfig[k]
      7 | }

      at Object.<anonymous> (lib/setenv.js:4:35)
      at Object.<anonymous> (lib/eda.js:1:1)

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        1.772 s
Ran all test suites.
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! @cf247/eda@1.0.2 test: `jest`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the @cf247/eda@1.0.2 test script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm WARN Local package.json exists, but node_modules missing, did you mean to install?

npm ERR! A complete log of this run can be found in:
npm ERR!     *******************************\npm-cache\_logs\2020-05-28T08_04_32_271Z-debug.log
*******************************consumers\packages\eda [CRMBROK-233 +0 ~3 -0 !]>

Not great but hey, first run and all!

The error message tells me everything I need to know:

ENOENT: no such file or directory, open '.env'

      2 | const fs = require('fs')
      3 | const dotenv = require('dotenv')
    > 4 | const envConfig = dotenv.parse(fs.readFileSync(`.env`))

Which is that it can’t find an .env file. And it wouldn’t. Later refactoring would remove this file dependency but for now, all I want to do is to get my test working.

This was the unit test code:

'use strict'

const eda = require('..')

describe('@cf247/eda', () => {
    it('no tests', () => {
    })
})

This is the code from the module it was importing via the require('..') statement:

require('./setenv')
const amqp = require('amqplib/callback_api');

module.exports = (io, emitter) => {
    ...

The top line is importing code from this file:

I’ve highlighted the problematic line of code

1
2
3
4
5
6
const fs = require('fs')
const dotenv = require('dotenv')
const envConfig = dotenv.parse(fs.readFileSync(`.env`))
for (const k in envConfig) {
    process.env[k] = envConfig[k]
}

The quickest (IMO) way to deal with this and move forward is to Mock the fs class. I did this by included a jest module mock into my unit test file:

I’ve highlighted the mock related code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
'use strict'
const fs = require('fs')
const eda = require('..')
jest.mock('fs', () => ({
    readFileSync: jest.fn((file_name) => {
        return []
    })
}))

describe('@cf247/eda', () => {
    it('no tests', () => {
    })
});

What this does is, when the readFileSync class function is called, it always returns an empty array []. As the unit code does not have a dependency on environment variables, this mocked response will work fine.

Closing thought

A filesystem mock is useful when it isolates one decision in the unit under test, and dangerous when it quietly becomes a fictional replacement for Node’s real I/O contract.

Last updated on