Skip to content

Instantly share code, notes, and snippets.

@StackTrac3
Forked from ryyppy/hard-dependency.js
Created January 29, 2021 10:26
Show Gist options
  • Save StackTrac3/10fa0c0b39933da75931b9c687fa9129 to your computer and use it in GitHub Desktop.
Save StackTrac3/10fa0c0b39933da75931b9c687fa9129 to your computer and use it in GitHub Desktop.
Jest: Module Mocking vs. Simple Dependency Injection
// ###############################
// runPackager.js
// ###############################
// In this example, the IO function is tightly coupled with the implementation
import { execFileSync } from 'child_process';
type RunPackagerOptions = {
projectRoot: string, // CWD the react-native binary is being run from
targetDir: string, // Target directory absolute or relative to projectRoot (e.g. 'rna/')
version: string, // Version number to for the build (semver)
platform: 'ios' | 'android',
entryFile: string, // File without any extension (e.g. 'index')
};
function runPackager(options: RunPackagerOptions): void {
// stuff happens here
// args = [...]
// opts = {...}
// Here, the IO effect takes place... this is our IO dependency
// we need to know is being called (in our tests, we need to know that
// this dependency is being used... we cannot read it from our FN signature)
execFileSync('react-native', args, opts);
}
// ###############################
// __tests__/runPackager-test.js
// ###############################
import runPackager from '../runPackager';
// In Jest, I need to mock child_process.execFileSync,
// jest will analyze the 'child_process' structure and replace
// every function with a mocked representation
js.mock('child_process');
describe('...', () => {
it('should use mocked execFileSync', () => {
// Get the jest mock function from the mocked module
const { execFileSync } = require('child_process');
// Do the stuff where the same execFileSync is being used
runPackager({});
// Check the output
expect(execFileSync.mock.call[0]).to.equal([]);
// Jest will reset the modules after every `it` case, so that's nice
})
});
// ###############################
// runPackager.js
// ###############################
// Here, we give the possibility to switch out the execFileSync implementation
// via a parameter option and default to the original implementation.
import child_process from 'child_process';
type RunPackagerOptions = {
// ...some attributes...
// Now, here we explicitly mention the IO dependency in our parameters
execFileSync?: typeof child_process.execFileSync, // IO dependency
};
function runPackager(options: RunPackagerOptions) {
// If execFileSync is not set, use the nodejs implementation
const { execFileSync = child_process.execFileSync } = options;
// stuff happens here
// args = [...]
// opts = {...}
execFileSync('react-native', args, opts);
}
// ###############################
// __tests__/runPackager-test.js
// ###############################
import runPackager from '../runPackager';
// Now, jest.mock() is unnecessary, since we know we
// can just hand in our execFileSync via parameter
describe('...', () => {
it('should use our execFileSync mock', () => {
// It's a nobrainer,... just create the function
// isolated from every other test run
const execFileSync = jest.fn();
runPackager({ execFileSync });
// Check the result... that's it. No clean up / reset needed
expect(execFileSync.mock.call[0]).toEqual([]);
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment