Skip to content

Instantly share code, notes, and snippets.

@ryyppy
Last active July 8, 2022 14:01
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ryyppy/e60376024aa9e4fe2962f3ab13e87bf0 to your computer and use it in GitHub Desktop.
Save ryyppy/e60376024aa9e4fe2962f3ab13e87bf0 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([]);
});
});
@ryyppy
Copy link
Author

ryyppy commented Sep 22, 2016

After playing around with jest.mock() I realized I can reduce this example by removing the verbose beforeEach stuff... in every it, the mocked modules will be reset... which is very convenient and isolates the tests well!

So there is only one advantage for Dependency Injection left: The dependencies are explicitly mentioned in the function parameters and we don't have to dive into the implementation details of the function to test.

@Vall3y
Copy link

Vall3y commented Mar 24, 2017

Thanks for the helpful snippets

By the way, at hard-dependency.js:38 I think you mean jest.mock instead of js.mock.

@alanhorizon
Copy link

Thank you so much for this; was having trouble wrapping my head around this, especially comparing the two approaches (tightly coupled, vs dependency injection) and testing each one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment