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
}
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();
});
});
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With