David Faizulaev
David Faizulaev

Reputation: 5741

Jest - getting error when mocking FS modules and calling config module

I'm writing unit tests with Jest trying to test a module which uses FS.

The module file:

import fs from 'fs';
import logger from './logger.utils';

export const getNumberOfFiles = async (targetDir: string): Promise<number> => {
  // get number of folders
  logger.info(`getNumberOfFiles from ${targetDir}/${fileName}`);
  const numberOfFiles = await fs.readdirSync(targetDir);
  return numberOfFiles.length;
};

Test file

import fs from 'fs';
import { getNumberOfFiles } from '../../src/utils/fs.utils';

jest.mock('fs');

describe('fs.utils', () => {
  describe('getNumberOfFiles', () => {
    it('Should return number', async () => {
      fs.readdirSync = jest.fn();
      const readdirSyncMock = fs.readdirSync = jest.fn();
      readdirSyncMock.mockResolvedValue([1, 2, 3]);

      const result = await getNumberOfFiles('targetDir');
      expect(result).toEqual(3);
      expect(readdirSyncMock.mock.calls.length).toEqual(1);
    });
  });
});

When I run the test file, I get the following error:

Config file ..../config/runtime.json cannot be read. Error code is: undefined. Error message is: Cannot read property 'replace' of undefined

  1 | const cheggLogger = require('@chegg/logger');
  2 | import loggingContext from './loggingContext';
> 3 | import config from 'config';
    | ^
  4 | import os from 'os';
  5 | import constants from '../../config/constants';
  6 | 

  at Config.Object.<anonymous>.util.parseFile (node_modules/config/lib/config.js:789:13)
  at Config.Object.<anonymous>.util.loadFileConfigs (node_modules/config/lib/config.js:666:26)
  at new Config (node_modules/config/lib/config.js:116:27)
  at Object.<anonymous> (node_modules/config/lib/config.js:1459:31)
  at Object.<anonymous> (src/utils/logger.utils.ts:3:1)

Content of logger.utils.ts

const internalLogger = require('internalLogger');
import loggingContext from './loggingContext';
import config from 'config';
import os from 'os';
import constants from '../../config/constants';

const logger = internalLogger.createLogger({
  level: config.get(constants.LOG_LEVEL)
});

export default logger;

I assume that config is using FS, and once I mock the module, it fails. How can I resolve this? Please advise

Upvotes: 2

Views: 562

Answers (1)

tmhao2005
tmhao2005

Reputation: 17514

I'm guessing the problem comes from config also using the fs api but you are now mock entire module fs which makes all methods should be mocked before using.

But I have an idea for you by using jest.doMock which you can provide a factory for each test and just mock only method we need. Here is a draft idea:

describe('fs.utils', () => {
  describe('getNumberOfFiles', () => {
    it('Should return number', async () => {

      jest.doMock('fs', () => ({
        // Keep other methods still working so `config` or others can use
        // so make sure we don't break anything
        ...jest.requireActual('fs'),
        readdirSync: jest.fn(pathUrl => {
          // Mock for our test path since `config` also uses this method :(
          return pathUrl === 'targetDir' ? Promise.resolve([1, 2, 3]) : jest.requireActual('fs').readdirSync(pathUrl)
        })
      }));
      
      // One of the thing we should change is to switch `require` here
      // to make sure the mock is happened before we actually require the code
      // we can also use `import` here but requires us do a bit more thing
      // so I keep thing simple by using `require`
      const {getNumberOfFiles} = require('../../src/utils/fs.utils');
  
      const result = await getNumberOfFiles('targetDir');
      expect(result).toEqual(3);
      // you might stop assert this as well
      // expect(readdirSyncMock.mock.calls.length).toEqual(1);
    });
  });
});

Just also want to check, if you created a config file as described here: https://www.npmjs.com/package/config#quick-start

Upvotes: 1

Related Questions