This post is a follow-up to an article I wrote a while back on Medium, called “NodeJS: Testing require() Ridden Code”. In that article I pointed out some of the problems with Node’s use of CommonJS, offered some solutions to testing it anyway and posed a stern warning at the end. However, I never fully felt satisfied with how I ended it.
It’s been nearly 2 years since I wrote that article, and I don’t disagree with my initial standpoint. The solutions I presented are a band-aid to an architectural problem. Additionally, it’s a problem that can be easily avoided. How?
Never export instances out of your application’s nested components / files. Export classes or functions instead.
In software development, particularly when discussing topics like Inversion of Control and Dependency Injection, there is a key piece of your application referred to as the Composition Root. Often times, in C-based programs this is your
main() function. In NodeJS, this is likely going to be whatever file you point
npm start at.
By following that simple bit of advice above, you no longer need to make decisions on where it is, or isn’t, okay to export an instance. You just don’t do it.
Instead, by exporting a class or function, you make the responsibility of the composition root to supply configuration or dependencies, and to wire up the instances in the proper order. You can absolutely use a dependency injection library to handle this for you, and you’ll likely want to, but it’s not necessary.
Now the coupling of components occurs in a single spot, and not across all the parts of your application. Testing the individual pieces becomes incredibly simple as the requirements of each function/class can be clearly defined.
One word of advice: If you decide to use a DI tool/library, pick one that stays out of your components. The simpler your code remains, the easier it will be to maintain. No need to clutter it up with another dependency in a different form.