Since I mentioned about functional / OO / and the dislike of jest mock, I feel like I should fill in some explanation here.
I'm not against jest.mock()
or any mocking library (such as sinon
).
I have used them before, and they definitely serve their purpose and is a useful tool.
But I find myself do not need them for the most part, and there is some tradeoff when using them.
Let me first demonstrate three ways that the code can be implemented without the use of mock.
The first way is functional, using a context
as the first argument:
// read-file-contents-sync.ts
import fs from 'fs';
export function ReadFileContentsSync({ fs } = { fs }, PathAndFileName: string): string {
if (PathAndFileName === undefined || PathAndFileName === null || PathAndFileName.length === 0) {
throw new Error('Need a Path and File');
}
return fs.readFileSync(PathAndFileName).toString();
}
// read-file-contents-sync.spec.ts
import { ReadFileContentsSync } from "./read-file-contents-sync";
describe('Return Mock data to test the function', () => {
it('should return the test data', () => {
const TestData:Buffer = new Buffer('This is sample Test Data');
// Trying to mock the reading of the file to simply use TestData
const fs = {
readFileSync: () => TestData
}
// Does not need to exist due to mock above
const ReadData = ReadFileContentsSync({ fs }, 'test-path');
expect(ReadData).toBe(TestData.toString());
});
});
The second way is to use OO:
// read-file-contents-sync.ts
import fs from 'fs';
export class FileReader {
fs = fs
ReadFileContentsSync(PathAndFileName: string) {
if (PathAndFileName === undefined || PathAndFileName === null || PathAndFileName.length === 0) {
throw new Error('Need a Path and File');
}
return this.fs.readFileSync(PathAndFileName).toString();
}
}
// read-file-contents-sync.spec.ts
import { FileReader } from "./read-file-contents-sync";
describe('Return Mock data to test the function', () => {
it('should return the test data', () => {
const TestData: Buffer = new Buffer('This is sample Test Data');
const subject = new FileReader()
subject.fs = { readFileSync: () => TestData } as any
// Does not need to exist due to mock above
const ReadData = subject.ReadFileContentsSync('test-path');
expect(ReadData).toBe(TestData.toString());
});
});
The third way uses a modified functional style, which requires TypeScript 3.1 (technically you can do that prior to 3.1, but it is just a bit more clumsy involving namespace hack):
// read-file-contents-sync.ts
import fs from 'fs';
export function ReadFileContentsSync(PathAndFileName: string): string {
if (PathAndFileName === undefined || PathAndFileName === null || PathAndFileName.length === 0) {
throw new Error('Need a Path and File');
}
return ReadFileContentsSync.fs.readFileSync(PathAndFileName).toString();
}
ReadFileContentsSync.fs = fs
// read-file-contents-sync.spec.ts
import { ReadFileContentsSync } from "./read-file-contents-sync";
describe('Return Mock data to test the function', () => {
it('should return the test data', () => {
const TestData: Buffer = new Buffer('This is sample Test Data');
// Trying to mock the reading of the file to simply use TestData
ReadFileContentsSync.fs = {
readFileSync: () => TestData
} as any
// Does not need to exist due to mock above
const ReadData = ReadFileContentsSync('test-path');
expect(ReadData).toBe(TestData.toString());
});
});
The first two ways provide more flexibility and isolation because each call/instance have their own reference of the dependency.
This means there will be no way that the "mock" of one test would affect the other.
The third way does not prevent that from happening but have the benefit of not changing the signature of the original function.
The bottom of all these is dependency management.
Most of the time a program or code is hard to maintain, use, or test is because it does not provide a way for the calling context to control the dependency of its callee.
Relying on mocking library (especially a mocking system as powerful as jest.mock()
) can easily get to a habit of ignoring this important aspect.
One nice article I would recommend everyone to check out is Uncle Bob's Clean Architecture: https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html