Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock a function with callback parameters in JestJS

My NodeJS app has a function readFilesJSON() that calls fs.readFile(), which of course invokes a callback with the parameters (err,data). The Jest unit test needs to walk both the error path and the data path.

My solution was to mock the call to fs.readFile() (see below). The mock function simply passes error or data based on test logic. This approach works when there is only one function being tested. The trouble I am seeing occurs when there are multiple functions that call fs.readFile(). Jest runs all the tests concurrently and the asynchronous nature of the functions mean that there is no guaranteed ordering to the calls to fs.readFile(). This non-deterministic behavior wrecks both the error/data logic and the parameter-checking logic using toHaveBeenCalledWith().

Does Jest provide a mechanism for managing independent usage of the mocks?

function readFilesJSON(files,done) {
    let index = 0;
    readNextFile();
    function readNextFile() {
        if( index === files.length ) {
            done();
        }
        else {
            let filename = files[index++];
            fs.readFile( filename, "utf8", (err,data) => {
                if(err) {
                    console.err(`ERROR: unable to read JSON file ${filename}`);
                    setTimeout(readNextFile);
                }
                else {
                    // parse the JSON file here
                    // ...
                    setTimeout(readNextFile);
                }
            });
        }
    }
}

The injected function setup looks like this:

jest.spyOn(fs, 'readFile')
    .mockImplementation(mockFsReadFile)
    .mockName("mockFsReadFile");

function mockFsReadFile(filename,encoding,callback) {
    // implement error/data logic here
}
like image 532
Lee Jenkins Avatar asked Oct 26 '25 17:10

Lee Jenkins


1 Answers

You can separate the different scenarios in different describe blocks and call your function after you clear the previous calls on the observed function, not to get false positive results.


import { readFile } from "fs";

import fileParser from "./location/of/your/parser/file";

jest.mock("fs");

// mock the file parser as we want to test only readFilesJSON
jest.mock("./location/of/your/parser/file");

describe("readFilesJSON", () => {
  describe("on successful file read attempt", () => {
    let result;

    beforeAll(() => {
      // clear previous calls
      fileParser.mockClear();
      readFile.mockImplementation((_filename, _encoding, cb) => {
        cb(null, mockData);
      });
      result = readFilesJSON(...args);
    });

    it("should parse the file contents", () => {
      expect(fileParser).toHaveBeenCalledWith(mockData);
    });
  });

  describe("on non-successful file read attempt", () => {
    let result;

    beforeAll(() => {
      // clear previous calls
      fileParser.mockClear();
      readFile.mockImplementation((_filename, _encoding, cb) => {
        cb(new Error("something bad happened"), "");
      });
      result = readFilesJSON(...args);
    });

    it("should parse the file contents", () => {
      expect(fileParser).not.toHaveBeenCalled();
    });
  });
});

like image 137
Teneff Avatar answered Oct 28 '25 08:10

Teneff