« Previous Tutorial Next Tutorial »

All right, we’re back with another episode of Five Minute React. In this tutorial, we’re going to write a basic test for our API, and watch it fail because we haven’t actually built the API endpoint yet. In the followup, we’ll build the endpoint and get the test running.

We’ve got Jest for testing and SuperTest for creating http requests – what we need is something to deal with the fact that http requests are asynchronous. SuperTest has built-in promises that can handle that, so let’s talk about those for a second, even though we’re not going to use them. Have you ever seen code that looks like this?

getData()
  .then(data => {
    manipulateData(data);
  });

If so, you’ve used (or at least looked at) promises. That code is saying “perform an asynchronous action, say hitting the DB for data, and then, once the data comes in, do something with it. It’s a way to avoid using tons of callback functions, which can lead to the aptly-named callback hell, where you end up nested ten tabs deep because you’re doing something like this:

getData(
  massageData(data,
    filterData(data,
      uppercaseData(data,
        displayData(data);
      );
    );
  );
);

That’s … well that’s just horrible. Promises help solve that. But a new feature of ES7, Async/Await, is even better. That same code up above would look like this:

const displayData = async (data) => {
  const data = await getData();
  const massagedData = await massageData(data);
  const filteredData = await filterData(massagedData);
  const uppercasedData = await uppercaseData(filteredData);
  return uppercasedData;
}

That’s much better … just a sequence of events, each awaiting the resolution of the previous line. Still perhaps a little confusing if you’re new to Async/Await, but don’t worry, we’ll work through it. Trust me, Async/Await is awesome and it’s what most developers will be using for asynchronous requests in the next few years. The good news is, the version of Node we’re using already supports it!

Now we need a file that lays out our test. Some people like to store tests in a separate folder, but I prefer to keep them in the same folder as whatever they’re testing. In this case, that’s our /routes/api folder, so let’s create a new file there and save it as users.test.js. You’ll note that we don’t have a users.js yet in our API folder. We’ll get there, promise.

Here’s how JavaScript testing works, on a basic level: you make assertions, and then test against them. Assertions are you telling the testing environment what should happen (or what to “expect”) when the test is run and succeeds. This means it’s important to write assertions in plain English that describe exactly what’s supposed to happen. For example, this is a bad assertion:

test('test the user API', () => {
   // user api test code
});

because it’s not telling us what behavior is expected. This is better:

describe('The User API', () => {
  it('Returns a list of all users'), () => {
    // user api test code
  });
});

because it explicitly explains what exactly is supposed to happen. This makes tests more readable, both in the code, and in the display that occurs when we run them. Note that the “describe” block is just for making our test results a little prettier. We could have multiple “it” blocks underneath that one describe, allowing us to group multiple tests against the users API.

All right, let’s use that code above to create our test. Here’s the code, with comments, but I’ll also explain what’s going on:

// Access supertest module functionality under the variable name "request"
const request = require('supertest');

// Top level of this test suite: the entire user API
describe('The User API', () => {

  // Specific test
  it('Returns a list of all users', async () => {

    // Connect to the server and get a response
    // Expect that response to be a 200 and serve JSON
    const res = await request('http://localhost:3000')
      .get('/api/users/list')
      .expect(200)
      .expect('Content-Type', /json/);

    // These expects are jest, not supertest
    // First, expect to get a result that is an array
    expect(Array.isArray(res.body)).toBe(true);
    // Second, expect the array to have something in it
    expect(res.body.length).toBeGreaterThan(0);
    // Third, expect the username of the first returned user to be Administrator
    expect(res.body[0].username).toBe('administrator');
  });
});

All right, let’s talk about that test before we run it. We’re bringing in SuperTest and assigning it to the variable request, as in server request, because that’s what SuperTest does – it makes testable requests to a server. As we know from previous tutorials, when you make a request to a server, you get a response in return.

Next, we use a describe function to add a top level header to our test, under which we define a specific test. See that async before the function we’re passing the test? That tells us not to continue until any await requests in the function are fulfilled, sequentially. We work with that on the next line, when we say that our res variable must await the server’s response.

Without this Async/Await block, we’d send a request out to the server but the test would just keep a-runnin’. res would be undefined or null, because the script is executing faster than the server can respond, and all our tests would fail.

Once we HAVE a server response, the Async / Await loop will complete and the test will continue on. Now res is full of our entire server response, which is a whole bunch of stuff, but what we really want is the body, which is the JSON that the API is sending back. From here, we can test that the body is an array, that it has something in it, and that the first (and only, for now) entry’s username is “Administrator”.

That’d be great, except as we’ve pointed out, we don’t have an API endpoint yet. Open package.json real quick and, in the scripts section, add the following line:

"test": "jest"

Save that. Now let’s run this test and watch it fail. Switch to your terminal or Command Prompt. Make sure MongoDB is running. Make sure your Express server is running. Now type:

yarn test

This runs our newly created test script, and we get the following unfortunate error:

Jest Error (Oh nooooooooo!)

That’s a big ugly error, but what it comes down to is: the test is failing because the server’s responding with a 404, and we were expecting a 200. Can’t test something that doesn’t exist. In the next tutorial, we’ll fix that. Catch you there.

« Previous Tutorial Next Tutorial »