When you're writing code that requires side-effecting dependencies (think libraries like axios), make your test-lyfe easier by adopting a lil' DI pattern, brought to you by vanilla JavaScript.
All that’s required is a closure. You’ll save yourself the grief of learning a mocking system like those that often come bundled with testing frameworks. While they can be useful, those mocking systems are often weirdly obtuse.
Say you have code like the following:
import axios from 'axios'
// Arbitrary example of something that looks real-world-ish
export default function(baseURL) {
const client = axios.create({
baseURL,
headers: { 'A-Header-That-I-Need': 'yep' }
})
return {
get: endpoint => client.get(endpoint),
post: (endpoint, payload) => client.post(endpoint, payload)
}
}
Looks pretty straight-forward, except that
axios
needs to be mocked during test time,
as it produces side-effects like get
and post
. Let’s just circumnavigate
the need for a mocking framework by using a closure for some dependency
injection.
import axios from 'axios'
function _factory(theDependency) { // One extra closure
return function(baseURL) {
const client = theDependency.create({
baseURL,
headers: { 'A-Header-That-I-Need': 'yep' }
})
return {
get: endpoint => client.get(endpoint),
post: (endpoint, payload) => client.post(endpoint, payload)
}
}
}
export const Test = {
_factory
}
// This right here. Just inject the dependency for the rest of the
// application. The rest of your code won't tell the difference.
export default _factory(axios)
From the perspective of the rest of our application code, nothing has changed,
but unit testing just got easier. Unit tests can be written with the Test
object instead of the default
export, and we can more easily assert things
about our code:
import { Test } from 'out-http-client'
// Some jest tests
describe('http-client', () => {
const get = jest.fn()
const post = jest.fn()
const fakeAxios = {
create: () => ({
get,
post
})
}
const client = Test._factory(fakeAxios)('https://an-base-url.biz')
it('it calls `get`', () => {
client.get(url)
expect(get).toHaveBeenCalled()
})
// Et cetera
})
Some slides -> http://thedrearlight.com/diy-di
Some examples -> https://github.com/killtheliterate/diy-di/tree/master/example