In the previous post I started testing the Node/Express application which I created before. So far we only added some tests to the Mongoose models and now I want to make sure, that the REST API keeps doing what I want it to do. In addition to using Mocha and Chai, which we looked at previously, now I will take a look at Supertest to make calls to Express and Sinon to stub calls.

Setup the project

If you don’t have the source code from the previous posts, you can get it from GitHub.

And in addition to what we already have, also install supertest and proxyquire to get started.

Unit testing Express routes

There are many ways to test Express routes, but most of the examples I found, which also use Mongoose/MongoDB are integration/functional tests.  Even though just yet our routes/controllers don’t do much more, than making single-simple calls to Mongoose and return the results, there is at least one exception.

To isolate the testing of the logic which we implemented in the User.delete method, I show an option with fake Mongoose models. As a result we can just focus on what’s implemented inside the callback.

As a reminder, the user deletion checks if the user is the author of an entry and rejects deletion if it is. Not a bulletproof protections mechanism, there is no locking, an entry might be created right after the check is done, but good enough for now.

Create fake models

In this unit test, I don’t want to test if the Model.findOne is working correctly. I just want to make sure, that if it returns an Entry object in the response (what it normally does if it finds one), my router/controller will save the user entry’s life. To completely isolate this code, I decided to replace the Mongoose model with a really dummy stub, which just responds as I tell it to. As the model is an integral part of the route, therefore I use proxyquire to detour the requests which would normally require a DB (later I’ll show how to do pretty much the same with Sinon).

In its simplest form, I can do it by writing a factory method to create a new Express application using the route which I want to test and creating fake models. We could also add test methods to the fake models, for example to report an error if the findByIdAndRemove was called when it shouldn’t been.

Run a simple supertest

Okay, so we have a test application, let’s run a simple test on it.

In this test, the fake Entry model returns the mockEntry object (which I defined before in the code) on find, so I expect the deletion will fail. Using supertest it sends a delete request and checks if the call returns with status 412 (this is what I set if precondition fails). Running the test looks okay, everything passed, but there is an ugly stack trace using up all my screen. This is because we use an express application with the default error handler and it will log the error to the console.

The complete test

Therefore I also add an error handler, similar to the one I have in app.js and as a result the complete test file looks like this:

Depending on what we want to test, we can also mix supertest with chai like:

Using Sinon to test Express with Mongoose

Now it was all nice and easy, I wanted to show proxyquire as an option to inject something and well, it was just okay. On the other hand there is a full fetched node module to all this job, so let’s see how to do it with SinonJS.

Install new dependencies

Stub call to Mongoose model

First see a simple example, stub the find call of the User model and check if the router returns the expected value. I put this one to a separate file, let’s say in test/routes/users.sinon.spec.js

All right, so just like before, we know exactly what comes from the find method of the User, in this case controlling it using Sinon. Send a request with Supertest and analyze the content a little bit with Chai.

We can add more tests, for example make an assumption, that the GET /users/:userId call returns 404 if no user was found by the findById call:

Just need to stub the User.findById call, like we did above with the find call, e.g. sinon.stub(User,'findById'); and User.findById.restore(); This test is wrong though, although in the app we implemented a default handler which throws 404, but we loaded the users router into a test app where we didn’t. Lucky for us, Express will also return 404 when we call next() in the users router and there is nothing to handle it, so it will work, but we don’t want to test Express.

Test the user deletion with Sinon

I guess this is the main attraction, let’s see how the user deletion test will look when testing it with Sinon, Supertest and Chai in the Mocha framework. The delete method calls findOne on the Entry model and findByIdAndRemove on the User model, will stub those.

Fix the user router

This was the final test, I tested both if the user deletion is rejected when the user owns an entry and also checked if the user deletion is executed when the user owned no entry. And failed. Here is why (from MDN):

In the first implementation of JavaScript, JavaScript values were represented as a type tag and a value. The type tag for objects was 0. null was represented as the NULL pointer (0x00 in most platforms). Consequently, null had 0 as type tag, hence the bogus typeofreturn value. (reference)

Due to this, the delete user call will always fail with 412, as the Entry.findOne either returns a real object or null. To fix this, in the routes/users.js I updated the check in the delete method as

Now it looks good:

The results

As always I’ve committed the results of this code to the training project. You can get it from GitHub:

 




Tagged with