
Written on 2022-07-05 by Marek Jędryka
Recently, I faced the following problem: I needed to test a service function using external resources such as database and REST API actions. My project uses ES Modules, function-oriented programming and is written in JavaScript, so I couldn't make it so easy. I couldn't use mocking tools like proxyquire or sinon.
The gist of ESM problem:
import { db } from '../db'
import { xmlFrom3rdParty } from './api/client'
export const serviceFn = () => {
const xml = xmlFrom3rdParty()
db.storeXMLResult(xml)
return 'done'
}
And that was the moment when I realized my project needed some refactorization. Hopefully, a small one.
Let me consider a few options...
I could introduce a configurable factory function returning the original function just configured by the closure. But it implies changes in usage of the function.
Module interface changes didn't look good for me, so I thought about it again.
I could export the default object from my service function file instead of exporting the function by value.
I don't want to do it because I want to keep chosen way of import/export modules. So I had to try something else, simpler maybe.
I exported brand new object including my dependencies for replacement or injection.
import { db } from '../db'
import { xmlFrom3rdParty } from './api/client'
// introduced external object
export const external = {
db,
xmlFrom3rdParty,
}
export const serviceFn = async () => {
// destructuring is needed for no changes in function block below
const { db, xmlFrom3rdParty } = external
const xml = await xmlFrom3rdParty()
await db.storeXMLResult(xml)
return 'done'
}
Pros:
Cons:
external object, let's say the simplest dependency container, in each function what uses external thingsChanges that I made let me inject dependencies in tests so I could do unit tests without whole universe interactions. Test cases can be fast, fully-controlled and predictable. It's a great plus.
And last but not least the changes did impact on the service function file and just added test. It doesn't impact the production code that uses the service function as well.