Using bound functions to unit test EcmaScript Modules
Imagine you have the following EcmaScript module you want to unit test:
// untestable.js
const configurationValue = 'something'
export function functionToTest () {
return configurationValue
}
The problem is you can’t test it for different values of configurationValue
(which, in a real example, might be based on a runtime quality of your app). You can’t because configuration value is private to the module.
However, if you rewrite your function like this:
// testable.js
export const context = {
configurationValue: 'something'
}
export const functionToTest = (function () {
return this.configurationValue
}).bind(context)
You can test it by providing different values for configurationValue
like this1:
// tests/index.js
import test from '@small-tech/tape-with-promises'
import { context, functionToTest } from '../testable.js'
const something = 'something'
const somethingElse = 'something else'
test('index-testable', t => {
t.equals(functionToTest(), something)
context.configurationValue = somethingElse
t.equals(functionToTest(), somethingElse)
t.end()
})
This works (both tests pass) because exports are live bindings.
Why not just use a class? Because sometimes you can’t. Case in point: I came up with this method to test my ES Module Loader in NodeKit.
ES Module Loaders are meant to export resolve()
and load()
functions and I was configuring them using module-level variables.
I didn’t want to change the function signatures to inject values or introduce conditional logic to support unit tests.
This method allows me to inject the values I need during tests. The only change to the code is exporting a bound function instead of an unbound function and referring to any context variables using this
.
Mocking whole modules
You can also use this method to mock imported modules.
For example, if the module you want to test looks like this:
// also-testable.js
import importedFunction from './other-module.js'
export const context = {
importedFunction
}
export const functionToTest = (function () {
return this.importedFunction()
}).bind(context)
And this is the module we want to mock:
// other-module.js
export default function () {
return 'actual value'
}
We can mock it like this in our test:
import test from '@small-tech/tape-with-promises'
import { context, functionToTest } from '../also-testable.js'
const mockFunction = function () {
return 'mock value'
}
test('index-testable', t => {
t.equals(functionToTest(), 'actual value')
context.importedFunction = mockFunction
t.equals(functionToTest(), 'mock value')
t.end()
})
Hope this adds another tool to your belt should you need it when unit testing EcmaScript Modules.
Like this? Fund us!
Small Technology Foundation is a tiny, independent not-for-profit.
We exist in part thanks to patronage by people like you. If you share our vision and want to support our work, please become a patron or donate to us today and help us continue to exist.
-
If you’re going to try this out, first
npm i --save-dev @small-tech/tape-with-promises
. You can run it withnpx tape tests/index.js
. ↩︎