In this story, I would like to cover the basics of Unit Testing and how it should be done. The test framework of choice is Jest.

So lets dive right in and take a look at the function that we are going to be testing:

//UtilityService.ts
import path from "path";
import fs from "fs";
const getAppVersion = () => {
const relativeFilePath = "../../downloads/output.json";
const outputFilePath = path.resolve(__dirname, relativeFilePath);
const outputFile = fs.readFileSync(outputFilePath, "utf8");
const output = JSON.parse(String(outputFile));
return output[0].apkData.versionName;
};
const utilityService = { getAppVersion };
export { utilityService };

First of all, we would need to decide what should be tested. What I think we should test for is the following:
1. Test that path.resolve is called with the correct inputs
2. Test that path.resolve returned with the final file path, which should be a String 
3. Test that fs.readFileSync is called with the correct inputs
4. Test that fs.readFileSync returned a stringified JSON object
5. Test that getAppVersion returned the version correctly

To test path.resolve , we would need to use the spyOn function that Jest has. This would look something likejest.spyOn(path, “resolve”) which would tracks calls to the actual function. The same is done for fs.readFileSync that we are using as well.

One thing you may need to know here is that we can mock the return value using .mockReturnValue()of a mocked function. What this would do is to simulate the return value of a function without caring about the inner processes. This is useful when we are not able to provide the same run time conditions for the tests. An example here would be mocking the return of fs.readFileSync to give us an expected return value. As you may have noticed, the return value of the fuction heavily depends on the file path that was supplied as part of the parameters. In our tests, that file may or may not be present in the directory which would give an error should the actual function be called. Hence, we can workaround that by usingspyFsReadFileSync = jest.spyOn(fs,“readFileSync”).mockReturnValue(mockReadFileSyncResponse) 

Here is an issue I faced while writing my tests.

What?! 20 times? How and where was I calling path.resolve 20 times? This really puzzled me and started a frenzy of debugging, trying to see what could cause this number to increase or decrease. It dawned on me that the problem was declaring the variables outside of a test block. This originally looked something like this:

...
describe(".getAppVersion", () => {
let relativeFilePath: any;
let originalFolderPath: any;
let filePath: any;
let mockReadFileSyncResponse: any;
let spyPathResolve: any;
let spyFsReadFileSync: any;
let version: any;
relativeFilePath = "../../downloads/output.json";
originalFolderPath = path.resolve(__dirname, "../");
filePath = path.resolve(originalFolderPath, relativeFilePath);
mockReadFileSyncResponse =
// tslint:disable-next-line:max-line-length
'[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":10300,"versionName":"1.3.0","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]';
spyPathResolve = jest.spyOn(path, "resolve");
spyFsReadFileSync = jest
.spyOn(fs, "readFileSync")
.mockReturnValue(mockReadFileSyncResponse);
version = utilityService.getAppVersion();
it("calls path.resolve with the correct directory and relative path", () => {
...

So just by using a beforeAll block, I was able to workaround this particular issue.

All that was needed is to move the variable assignments and function calls inside of the beforeAll block. 

...
describe(".getAppVersion", () => {
let relativeFilePath: any;
let originalFolderPath: any;
let filePath: any;
let mockReadFileSyncResponse: any;
let spyPathResolve: any;
let spyFsReadFileSync: any;
let version: any;
beforeAll(() => {
relativeFilePath = "../../downloads/output.json";
originalFolderPath = path.resolve(__dirname, "../");
filePath = path.resolve(originalFolderPath, relativeFilePath);
mockReadFileSyncResponse =
// tslint:disable-next-line:max-line-length
'[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":10300,"versionName":"1.3.0","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]';
spyPathResolve = jest.spyOn(path, "resolve");
spyFsReadFileSync = jest
.spyOn(fs, "readFileSync")
.mockReturnValue(mockReadFileSyncResponse);
version = utilityService.getAppVersion();
});
it("calls path.resolve with the correct directory and relative path", () => {
...

Here is a sample of the test file that covers all the scenarios that I mentioned above:

//__tests__/UtilityService.ts
import path from "path";
import fs from "fs";
import { utilityService } from "../Utility";
describe(".getAppVersion", () => {
let relativeFilePath: any;
let originalFolderPath: any;
let filePath: any;
let mockReadFileSyncResponse: any;
let spyPathResolve: any;
let spyFsReadFileSync: any;
let version: any;
beforeAll(() => {
relativeFilePath = "../../downloads/output.json";
originalFolderPath = path.resolve(__dirname, "../");
filePath = path.resolve(originalFolderPath, relativeFilePath);
mockReadFileSyncResponse =
// tslint:disable-next-line:max-line-length
'[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":10300,"versionName":"1.3.0","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]';
spyPathResolve = jest.spyOn(path, "resolve");
spyFsReadFileSync = jest
.spyOn(fs, "readFileSync")
.mockReturnValue(mockReadFileSyncResponse);
version = utilityService.getAppVersion();
});
it("calls path.resolve with the correct directory and relative path", () => {
expect(spyPathResolve).toHaveBeenCalledTimes(1);
expect(spyPathResolve).toHaveBeenCalledWith(
originalFolderPath,
relativeFilePath
);
});
it("gets correct output file path", () => {
expect(spyPathResolve).toHaveReturnedWith(filePath);
expect(filePath).toEqual(expect.any(String));
});
it("calls fs with the correct file path and encoding", () => {
const encoding = "utf8";
expect(spyFsReadFileSync).toHaveBeenCalledWith(filePath, encoding);
});
it("fs returns the stringified json object", () => {
expect(spyFsReadFileSync).toHaveReturnedWith(mockReadFileSyncResponse);
});
it("returns the version correctly", () => {
expect(version).toEqual("1.3.0");
});
afterAll(() => {
jest.restoreAllMocks();
});
});