Aral Balkan

Mastodon icon RSS feed icon

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.


  1. If you’re going to try this out, first npm i --save-dev @small-tech/tape-with-promises. You can run it with npx tape tests/index.js. ↩︎