Editor’s note: This blog post from May 2013 has been updated with current information.

Testing an API with Node.js is dead simple. You can quickly write tests for any API, regardless of its language.

[Note: You can get all the code examples used in this post from GitHub.]

Hackers Don’t Test

I don’t like writing tests. Just ask any of my coding teammates. I spent too many years slapping together quick protoypes that were just for proof of concept and didn’t need to be maintained. I think of testing like financial accounting; I know it’s vital to keep operations running smoothly, but I much prefer if somebody else handles it.

I’m getting better though, and it’s paying off. You’ve probably already read about the reasons you should write tests. If you’re still not convinced, consider this: it’s quickly becoming table stakes for most modern web development projects, much like knowing how to use Github. If you want to contribute to open source projects, or make it past your first interview, you’ve gotta write tests.

Unit, Integration, Functional, and Acceptance

I’m going to skip the philosophy and finer points. Read this answer for a great explanation of the different types of tests. On the most granular level we have unit tests. On the other end of the spectrum were have browser-testing tools like PhantomJS or SaaS options like BrowserStack and Browserling. We’re going to be closer to that high level testing, but since this is purely an API, we don’t need a browser-level tool.

Our Example API

Let’s take a look at our example API, which is a protected pretend blog. In order to show off some of the testing options, this API:

  • requires Basic Auth
  • requires an API key be passed in as a custom header
  • always returns JSON
  • sends proper status codes with errors

Mocha, Chai, and SuperTest

If you’ve spent 30 minutes tinkering with Node.js, there’s a really good chance you’ve seen the work of TJ Holowaychuck and his army of ferrets. His Mocha testing framework is popular and we’ll be using it as a base. Fewer people have seen his SuperTest library, which adds some really nice shortcuts specifically for testing HTTP calls. Finally, we’re including Chai just to round out the syntax goodness.

var should = require('chai').should(),
    supertest = require('supertest'),
    api = supertest('http://localhost:5000');

Remember, we’re passing in the base URL of our API. As a sidenote, if you’re writing your API in Express, you can use SuperTest to hook write into your application without actually running it as a server.

Test Already!

Install Mocha (npm install -g mocha) and check out the getting started section. To summarize, you can group little tests (assertions) within it() functions, and then group those into higher-level groups within describe functions. How many things you test in each it and how you group those into describe blocks is mostly a matter of style and preference. You’ll also evenutally end up using the before and beforeEach features, but our sample test doesn’t need them.

Let’s start with authentication. We want to make sure this API returns proper errors if somebody doesn’t get past our two authentication checks:

See the gist

Don’t let the syntax on lines 3 and 10 throw you; we’re just giving the tests names that will make sense to us when we view our reports. You can put pretty much anything in there. In both tests (the it calls), we make a get call to /blog.

In our first test, we use set to add our custom header with a correct value, but then we set put some bad credentials in auth (which creates the BasicAuth header). We’re expecting a proper 401 status.

In our second test, we set the correct BasicAuth credentials but intentionally omit the x-api-key. We’re again expecting a 401 status.

Run Nyan Run!

We’re almost ready to run these tests. Let’s follow TJ’s advice:

Be kind and don’t make developers hunt around in your docs to figure out how to run the tests, add a make test target to your Makefile.

So here’s what that looks like:

TESTS = test/*.js
  mocha --timeout 5000 --reporter nyan $(TESTS)

.PHONY: test

The two flags in there are increasing the default timeout of 2000ms and telling Mocha to use the excellent nyan reporter.

Finally, we jump over to our terminal and run make test. Here’s what we get:

Nyan cat showing failing test

Uh oh. Looks like the API is returning a message that looks like an error, but the status is 200. Let’s fix that in the API code by changing this:

res.send({error: "Bad or missing app identification header"});

to this:

res.status(401).send({error: "Bad or missing app identification header"});

Expect More

Like all good developers, I’m going to be confident that I’ve fixed it and move on before re-running the report. Let’s add another test:

See the gist

This one has an “end” callback because we’re going to be inspecting the actual results of the response body. We send correct authentication and then check four aspects of the result:

  • line 7 checks for 200 status
  • line 8 makes sure the format is JSON
  • line 11 is a chain that checks for a posts property, and also makes sure it’s an array.

It’s Chai that’s giving us that handy way of checking things.

Run the tests again…wait 46ms…and…

Nyan cat showing all tests passing

Happy Nyan!

Hackers Don’t Mock

Another common component of testing is the notion of using “mock” objects and/or a sandboxed testing database. What I like about the setup we’ve covered here is that it doesn’t care. We can run our target server in production or dev or testing mode depending on our purpose and risk aversion without changing our tests. Finer-grained unit testing and mock objects certainly have their place, but a lot can go wrong in between those small abstracted pieces and your full production environment. High-level acceptance tests like the ones we’ve built here can broadly cover the end user touchpoints. If an error crops up, you’ll know where to start digging. While it’s not necessary to get into the weeds with unit testing, there’s still significant value in prototyping your entire API… if you’re using the right tools.

Get Dirty in the Sandbox

Even though the acceptance testing we’re using above can be safely deployed into prod, dev, or test to reveal high-level errors, building a prototype of your API in a sandbox may help avoid any surprises once your API is in the wild. LoopBack makes building and modeling APIs so easy that there’s no reason to cut corners. When your REST API is ready for prime time, use IBM API Connect to easily convert your mockup into an enterprise-ready REST API.