Unit testing, for me, it’s a big deal, it just helps me have more confidence in the code that I’ve written, it helps me feel reassured that things are going to work as expected. Not to mention that when you show good quality metrics to upper management in bigger companies, they love seeing all those little boxes ticked. In some companies they may have policies in place that will simply prevent you from having code that doesn’t have x% of the code base covered by unit testing, I should know as I’ve been one of those people to go around implementing such policies in place.
But let’s just say that you have the same solution, only now you’re going to write it in Java, using the likes of SpringBoot. I’d personally be stunned if you didn’t use dependency injection here, it makes sense, dependency injection is an amazing tool! 🤘
So, why don’t we use dependency injection with Node? Sure, you can’t have dependency injection in quite the similar sense where you just rely on annotations, or in the context of TypeScript, decorators. But we have constructors in both Node & TypeScript right? We have the ability to make factories, wouldn’t you agree? We have currying, etc.
You can see we have some function that allows someone to create a company, simple enough, it validates the arguments, then it’ll use the
model to go & create that company in the data layer, whether that’s a RDBM solution or simply a bunch of JSON files, that’s going off topic. It’s simply enough, using basic HTTP response codes, you can see that it’ll notify the user when something has gone wrong too, cool!
But you can also see that there’s some quite tight coupling going on here, where we rely on the
service & the
model to be in the same folder directory, never-mind anything else. So how can we improve this so that it makes unit testing easier, because at this moment in time, you’d have to rely on a tool like Jest to provide mocks for both of these files. Okay, if that works, that works, but isn’t there just an easier way?
How about this? Where we’ve just used a cute little factory-sorta pattern? How about that? I say sorta pattern because I know that a lot of developers can be extremely anal about naming things & I find those arguments stupid, I’ll be honest, I used to be one of those guys, e.g. “this isn’t a factory because of x, y & z”. But I soon realised, who cares? As long as it works, works well, is clean & easy to understand, etc, who cares? That’s why I try to use more generic terms these days, like with unit testing specifically, when people argue whether something is a mock or a spy or a stub, why not just call it a fake? That way no one cares, it can be any of the three?! 😅
But back to the topic at hand! ✋ – Here you can see how this might be a little bit easier than the original solution, because it’s the same thing so to speak, but here you can see that we’re allowing ourselves to inject the
service & the
model whenever it suits us. Not to mention, this also means that we can throw these files wherever the hell we like now, this sorta refactoring pushes the responsibility of how these files communicate with one another down to the consuming code base & if you push things down enough, you’ll get to a point where you might have something like this.
Which can then allow your consuming code base to look a little something like this:
Now wouldn’t you agree this makes your life easier when trying to achieve full test coverage, as within your tests, you can inject whatever the hell you want, I mean as long as you have those methods defined or whatever, you can do as you please.
Now, wasn’t that an easy way to get some decent test coverage right there? Sure you might not have 100% unit test coverage right there, but at least it’s an easy way to help you improve the ease of improving test coverage, without using dependency injection coupled with currying & relying on the more primitive approach, it results in making automated testing that little bit more unpleasant, at least in my opinion anyway.
With TypeScript this sorta thing gets even easier, because you can just rely on TypeScript’s solution for dependency injection. Or you could try out the likes of tsyringe which is also made by Microsoft, so it’s probably a pretty decent solution for dependency injection, I wouldn’t really know, I’ve not used it myself.
That’s the thing with unit testing too, you probably shouldn’t get carried away with it to the point where you’re going beyond the scope of testing the code & the logic that you’ve written, to the point where you’re relying on complete, external systems. That kinda introduces another world of complexity in some scenarios & it just makes your life harder, e.g. connecting to a database & making database queries, if you’re able to do it relatively easy with unit testing, hell, more power to you! But that sorta stuff can be quite hard.
Thankfully with Node, that specific example is relatively easy, e.g. you can use SQLite & knex to handle that sorta stuff, but what about when you have an FTP server? … What about when you have an external queue?… You get the idea, in some cases it’s better to cover the fundamentals, the actual business logic that you’ve developed, then rely on a more mature form of testing to ensure that all of these components work together, as expected. I mean isn’t that essentially the core nature of BDD? Where you may have some form of specification document that looks a little something like this:
You can then create specific test cases from there, e.g. a list of usernames & passwords that you know should test & should fail, etc. I mean this is going slightly off topic now, but you can see how applying these simple ideas, it can help improve the maintainability of the code base & the over all testability of the code base.