patternjavascriptMinor
Mocking config data in JavaScript unit tests
Viewed 0 times
mockingjavascripttestsconfigdataunit
Problem
I'd really like someone to sanity check my approach for unit testing the
Background
The tests
I'm testing the
My tests work, but I feel it has a few issues:
configs.js
summarise.js
`import { getOptionConfig } from './config';
/**
* Summarise an option's values
* @param {string} optionName - an option name, used for referencing its config data
* @param {array} optionValues - application state. values of a particular option
* @returns {string} a summary of the option's values
*/
export const summarise(optionName, optionValues) {
const optionConfig = getOptionConfig(optionName);
if (optionConfig.exclude) {
return '';
}
if (optionConfig.type === 'red') {
return optio
summarise() function and mocking its dependencies.Background
- Each option has a set of values, which come from the app state
- Options also each have a config, which are defined in
configs.js(potentially a large list, with 'dynamic' data-like variations)
The tests
I'm testing the
summarise function. To isolate my tests, I've mocked the option configs. This means I don't have to couple the test to certain option configs, which allows settings to be changed freely.My tests work, but I feel it has a few issues:
- I can't directly spy on a config object, so I've exported a function
getOptionConfig()which I can spy on. I feel like it would be cleaner to avoid an API; and just spy on the object if possible.
- I feel it'd be much cleaner to pass the
_optionConfigsobject tosummarise()(to avoid mocks completely). However, this would only pass the same issue to any functions which callsummarise().
- Because I'm forcing a return value in the spies, I'm not testing the parameter that I'm passing to
getOptionConfig(). Is this bad?
configs.js
const _optionConfigs = {
exampleOption: {
type: 'red',
},
exampleOption2: {
type: 'blue',
},
exampleOption3: {
type: 'red',
exclude: true,
},
exampleOption4: {
type: 'red',
},
// ... the list goes on...
};
export const getOptionConfig(id) {
return _optionConfigs[id];
}summarise.js
`import { getOptionConfig } from './config';
/**
* Summarise an option's values
* @param {string} optionName - an option name, used for referencing its config data
* @param {array} optionValues - application state. values of a particular option
* @returns {string} a summary of the option's values
*/
export const summarise(optionName, optionValues) {
const optionConfig = getOptionConfig(optionName);
if (optionConfig.exclude) {
return '';
}
if (optionConfig.type === 'red') {
return optio
Solution
I agree with point number 2, just passing in the config is better (i.e. dependency injection).
You could do it like this:
Now summarize can be used like before, and in your tests you can simply test the unbound function.
This kind of pattern should be used in all your code, and will make it a lot easier to test.
However, binding up functions like this becomes a little tedious (with the extra name "summarizeWithConfig", the ugly bind syntax, etc), so I would really recommend
using classes instead, and have the constructors of those classes as injection points.
You could do it like this:
// Definition of summarize:
export const summarize(config, optionName, optionValues){
// stuff
}
// In some other file:
const summarizeWithConfig = require('./summarize');
const config = require('./config');
// Create a new function which has it's first argument bound to the config
let summarize = summarizeWithConfig.bind(null, config);Now summarize can be used like before, and in your tests you can simply test the unbound function.
This kind of pattern should be used in all your code, and will make it a lot easier to test.
However, binding up functions like this becomes a little tedious (with the extra name "summarizeWithConfig", the ugly bind syntax, etc), so I would really recommend
using classes instead, and have the constructors of those classes as injection points.
Code Snippets
// Definition of summarize:
export const summarize(config, optionName, optionValues){
// stuff
}
// In some other file:
const summarizeWithConfig = require('./summarize');
const config = require('./config');
// Create a new function which has it's first argument bound to the config
let summarize = summarizeWithConfig.bind(null, config);Context
StackExchange Code Review Q#135972, answer score: 2
Revisions (0)
No revisions yet.