Fun with Stamps. Episode 17. Easy 100% unit test coverage in JS

Vasyl Boroviak
Jul 23, 2017 · 6 min read

Hello. I’m developer Vasyl Boroviak and welcome to the seventeenth episode of Vasyl Boroviak presents Fun with Stamps.


TL;DR

Using this simple technique you can cover basically any JavaScript code with unit test. You would not need proxyquire or any other module mocking. You would not need messing around with setTimeout or any other builtin JavaScript API. The code and the tests will still be easy to read and maintain.

And, of course, we will utilise stamps to make it even easier. The main part — unit tests — are in the middle of this article.

All the source code can be found here: https://github.com/koresar/fws-ep17

Let’s write a node.js app

The node.js application code below is rather complicated to quality cover it with unit tests. It was chosen to show you how even the most complex code can be covered in JavaScript using stamps.

A production ready node.js service typically looks like this. I’m using the express-generator CLI to generate our app.js.

As you can see there is already a lot of dependencies which might cause troubles while writing unit (not integration!) tests for that file:

Your task

You should make sure:

Yes, we are going to have all that listed above. But additionally:

To have that all we will use the simple technique called early dependency injection utilising stamps.

The main and only refactoring

To create our app2.js I’m going to add this. to few variables. So, instead of keeping them in the function scope we will save them to an object. Wait saying “Booo!” You have to see how the unit tests look after the refactoring. It’s important!

Take a look at how we prepared our code for the dependency injection. As you can see we pushed all the problematic dependencies to the this. object using stamp’s props. And we moved the two route handlers to the stamp’s methods down below.

Also, the module does not export a function any more, but a stamp. The stamp will start the service when an object instance is created from it.

So, now we can change dependencies just before starting the service. Like so:

let App = require('./app')
App = App.props({
http: myMockedHttpModule
})
App() // start the service

In the code above I replaced the actual http module with something like a mock object:

let myMockedHttpModule = {
createServer() {
return {
on() {},
listen() {}
};
}
};

Now you can test the “Application” and the “Routing” parts of the app (see the comments separating the file onto three sections) without opening any sockets! Just use that mocked App from the code above in all your tests.

Unit tests

“Application” part of the service

At the top of this file we are creating an http dependency which does not open any ports. Instead of the actual .listen() method we put an empty function.

As you can see we are setting the http dependency only once (that’s called the early dependency injection) and then transparently reusing it in the all the tests of this file. So that the it() tests look clean.

Look closer at the tests. We mock only the only relevant dependencyexpress or bodyParser. This hugely improves readability!

The first test makes sure that we do actually share the ./public directory.

The second test makes sure that any time we share a directory we actually share the /public folder(s). No matter how many times the .static() function is called.

The third test makes sure that urlencoded({extended}) option is false.

It is hard to over stress the point that the tests are not bloated, that they are simple, and anyone with the basic JavaScript knowledge can read and amend them as needed.

“Routing” part of the service

If you read this 5 minute article — Episode 1. Stamp basics — you will find that every stamp has the .compose property. It’s a function which also serves as the metadata holder (aka “stamp descriptor”).

Philosophy of stamps: you are allowed and encouraged to extract and manipulate a stamp’s metadata.

At the top we retrieve the two route handling methods from the metadata — handleNotFound and handleGenericError. And then we simply unit tests them as regular functions.

That’s how you can extract each individual method (or property, or initializer, or static property, etc) and test it without creating an instance of that stamp.

“Server” part of the service

This is the most complicated test. We mock three things: the http module, the setImmediate JavaScript global function, and the process global node.js variable.

The done() function is executed only after we made sure that the setImmediate and process.exit were actually called.

This test would have been simpler if we would move few other variables to the props of the stamp. However, my point here is to show you that a code of any complexity can be covered with simple unit tests; and that you don’t need any sophisticated NPM modules for mocking or dependency injection.

All you need is to try stamps.

More complex testing scenarios

Any future complications of the app2.js will still be easy to unit test.

For example, what if you would need to open a Postgres database connection before opening the HTTP port?

Easy! Just push it to the stamp’s props like we did with the http module dependency. (Try it by forking this repository.)

But, if you would attempt writing this unit test using the original app.js approach you’d need to put more effort mocking the database dependency.

Conclusion

It’s difficult to unlearn the typical way we do software development. Classes or pure functions are both great ways to code in JavaScript. Although, I do believe that by using stamps we can all be more productive as developers.

When I code, my typical .js files export a single thing — a stamp. There is a number of positives in this approach. I’ll list them in the order of importance.

Have fun with stamps!


Vasyl Boroviak

Written by

I’m a Ninjineer!!!

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade