Testable JavaScript
I’ve recently been trying to improve myself a lot, I’m always trying to improve on my technical abilities, always. Whilst looking more & more into things, I’ve stumbled upon quite an unpleasant reality, which is that there’s next to no real world examples on how you can write your JavaScript code to make unit testing easier.
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 when it boils down to improving your quality of testing with JavaScript, if you’ve not developed your code in a specific way, it can make unit testing a lot harder, it might mean that you need to rely on jest to create mocks, etc. That’s not strictly a bad thing, as long as the application works & you’ve got some good unit test coverage, to me that’s all that really matters. I mean to a lot of people within the business, they may not even care if you’re doing unit testing or not, your mileage will most certainly vary there.
Leaving TypeScript out of the equation here, because that can make life a lot easier with unit testing & writing your code to make it more test friendly, but to an extent, you could apply some of my points to some extent, maybe. But fundamentally, especially with more modern JavaScript based solutions, I see people importing all of their dependencies at the top of the file & not having a care in the world. I mean I’ve done it myself, plenty of times, you see other developers doing it all the time, I mean fair enough? It’s quite reasonable to agree that it’s a logical approach to do something like this, why not right? 🤔
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.
So, enough of trying to make you think, here’s an example that I quite like to improve the quality of the code that I write when using JavaScript, let’s say that we have this function here, it’s simple enough, so I believe, but it’s good enough to help me illustrate my point:
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.
Conclusion
Before I get overly opinionated on anything, I’ll just say that a lot of developers within the Node, JavaScript & TypeScript community really need to start thinking about these sorta subject areas more often. To this day, I’ve not had the luxury of working with another developer, that when they’ve been using some variation of JavaScript, they’ve gone ahead & made their code reasonably testable, usually I’ve had to hack something together or do a bit of a refactor in order to get reasonable test coverage.
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.