28. API test setup

In order to test our endpoints, we'll need to be able to interact with our Express server from our test spec files.

All our endpoints are part of our Express instance, so let's export this instance and then we can import it into our tests.

So at the bottom of our server file we can go:


module.exports = app;

Now our server file is actually a module.

So coming over to our item test file, let's first delete the demo test, and import our Express app module at the top.


const expect = require("expect");
const app = require("../../../server");

Describe block

We will very often create multiple unit tests for the same feature in order to test different aspects of that feature; for example, what happens if we supply bad data in the request.

We can group related tests inside a describe block. This doesn't change the functionality of the included tests, but it means the code, as well as the output, will be more logically grouped.

In each of our API test files, we'll be grouping tests based on the endpoint. So let's create our first describe block for the "POST /items" endpoint.

The second argument to describe is a function that will simply include the test cases.


describe("POST /items", () => {


First test

Our first test case for this endpoint will test if a new item is created when the proper data is provided. So in a moment we'll create a JSON payload and test that the API returns a success message.

To begin with, let's call it and the description of the test will be "should create a new item".

This will be an async test since we need to wait until the database call is complete before returning a value, so let's create an async callback function.

We'll then just put in a dummy assertion in for now so we can ensure this test suite is working.

So we'll go expect(true).toBe(true);.


describe("POST /items", () => {
  it("should create a new item", async () => {

Conditionally listening

If you try to run thhis test suite now with npm run test and your server is still running, you'll get an error about the server port already being in use.

This is because the test suite is creating another instance of the Express server and is trying to bind that on the same port as the first.

In fact, the test suite doesn't need the server to bind to any port at all; we'll see how we can directly trigger our endpoints without the Node web server running in just a moment.

So for this reason, let's make it so our server module will not create a server if called in the test suite.

To do this, in our main server file, wrap the listen call in a conditional. That condition will be process.env.NODE_ENV does not equal "test".


if (process.env.NODE_ENV !== "test") {
  app.listen(port, () => {
    console.log(`Listening on port ${port}`);

Now we just need to ensure we set this environment variable in our script:

"scripts": {
  "test": "NODE_ENV=test mocha tests/unit/server/*.js"

So when you run the test via the command line again, the environment variable will result in the condition failing, and the listen call will be skipped.

Cross env

How we've set that environment variable is not compatible with every system, though. I believe Windows has a different syntax for setting environment variables on the command line.

So to make this work universally, we can use the cross-env package.

So let's install that by going:

$ npm i -S cross-env

Now we can update our test script and make the environment variable an argument of cross-env. This will ensure that this command will now work universally:

"scripts": {
  "test": "cross-env NODE_ENV=test mocha tests/unit/server/*.js"

Exit after tests complete

If we run our tests now, you'll see that everything seems to work.

However, you'll notice that the command line process doesn't actually end after the tests have finished. You have to manually kill it with Ctrl + C.

This is because, by default, Mocha will not kill the server itself. But we can change that by adding an --exit flag to our script like this:

"scripts": {
  "test": "cross-env NODE_ENV=test mocha tests/*.test.js --exit"