Skip to main content

Closures

Description​

Create a factory function that takes the services needed and returns a function or object that encloses those services.

In production code, use the real services to construct the module, by either:

  • Creating and exporting the constructed production implementation alongside the factory function
  • Calling the factory function in application code to create the production implementation

In tests, configure the dependencies for testing by calling the factory function with test-friendly values.

Example​

api.ts

// production api implementation
export const api = {
getWidget: (id) => { ... }
}

widgets.ts

import { api } from './api'

// factory function
export const makeWidgets = (widgetApi) => {

// return an object that uses the api
return {
getWidgetName: async (widgetId) => {
const widget = await widgetApi.getWidget(widgetId);
if (!widget) return "Unknown Widget"
return widget.name
}
}
}

// export the production version, this is imported by other modules
export const widgets = makeWidgets(api);

widgets.test.ts

import { makeWidgets } from './widgets'

describe("getWidgetName", () => {
test("unknown widget returns 'Unknown Widget'", () => {
const fakeApi = {
getWidget: async () => undefined
}

// use the factory to make the object we want to test, with a fake api implementation
const widgets = makeWidgets(fakeApi)

const name = await widgets.getWidgetName(42)

expect(name).toEqual('Unknown Widget')
})
})

Notes​

This is a general technique that can be used anywhere, not only in react-aware code.

This is very similar to using classes that take dependencies as constructor objects.