In this lesson, we will use Chai's request method to test our Node application's API responses. By the end of this lesson, you will know how to:
[00:02] I've got a very generic Express page in my browser here that displays the standard, "Welcome to Express" text. We're going to start by creating unit tests for this page. Then we'll add some additional functionality and test that as well.
[00:14] Our first step is to install Mocha. I like to have it installed globally, so I'll use the -g flag. We need some specific packages in our application. I'll do npm install chai --save -dev. Chai is our assertion library that allows us to do things like say a string should equal foo, or it should have a length of three.
[00:37] You'll see in detail how it works as we proceed through the lesson. Next, I'll install Chai HTTP. That allows us to test HTTP functions in our application. You'll notice both of those were installed with the --save -dev flag. If I open up my package.json file here, you'll see that they're listed in the devDependencies section.
[01:03] That's done so that these packages are only loaded whenever I run in development, and not in production. That prevents them from consuming resources in production when they aren't needed.
[01:15] I'm going to create a folder in my application called test. Mocha knows, whenever it runs, to look for this folder and run all the tests inside of it. Inside of the test folder, I'll create a new file, and I'm going to call this index_test.js.
[01:30] This is a fairly common convention. The name index tells you the name of the file and the application being tested. In this case, we're going to test index.js. _test tells you it's a test file, and then .js indicates that it contains JavaScript code.
[01:49] Inside my test file, I'm going to require Chai, I'm going to require Chai HTTP, and I'm going to define Should, and that's going to equal chai.should. That actually refers to the Chai Should assertion library. Chai supports Should, Expect, and Assert interfaces. You can use whichever one you're more comfortable with.
[02:12] Finally, I'll define my server. That's going to point to the app file and the root. This will allow Mocha to start the application and test it properly by using the exact output from the server.
[02:26] Finally, I'll set chai.use to chai-http. That lets Chai know that it should be using the Chai HTTP request library for our calls. Now we're ready to write a test. Our tests follow a describe it block syntax, and it looks like this.
[02:45] We'll say describe, and then we'll pass in a string that describes what we're testing. This is an ES6-style function, so we'll have empty braces followed by a fat arrow, and then our curly bracket. Then we have our IT statement. We'll say it returns the home page.
[03:01] Again, this is a description of what the test should actually be doing. It's another ES6 function. This time we have a parameter called done, followed by our fat arrows. Now, we can say chai.request, and request our server.
[03:18] We'll do a .get, which is a GET HTTP method. We're going to get the endpoint. On end, we're going to provide a function with our standard error response objects. This is where our Should assertion library comes into play.
[03:34] We'll say our response should have a status of 200. I passed in this parameter called done, right up here. What that does is for asynchronous operations, we'll call that parameter whenever the async operation's finished.
[03:48] We'll do that now. That lets the test know that everything is completed, and it's OK to test for the assertions. That's all we need for our test. I can open up a terminal. I can type mocha, and it automatically traverses the directories, finds my tests, and we have one passing test. Let's add a little bit more functionality to this test.
[04:11] Let's say that our response text should contain, "Welcome to express." I save that, return to my terminal, rerun it. We still have one passing test. Since both of these tests are contained in the same it block, it's considered one passing test.
[04:29] To show you what it looks like if that fails, let's change "Welcome to express" to "Foo." We'll save it, rerun the test. We get a failing message. This is where the strings we used to name our tests come in handy, because we have /get returns the home page, and when the test fails, it tells us exactly which test failed.
[04:50] Then it gives us a description here, an uncaught assertion error, and then it shows you what it actually receipts, so it expected in all of these tests to include the word foo, which it clearly doesn't. If I change that back to welcome to express, save it, and rerun the test, everything is passing again.
[05:11] Now, let's add and add route to our application that accepts two numbers, and provides the sum of those numbers in the response. To do this, I'm going to use test-driven development, meaning that I'm going to write the test first, and then the code required to make that test pass.
[05:27] This is a technique that helps us to ensure all the code that I write has a test for it, versus writing the code first and then figuring out how to test it. The first thing I'll do is create a new file for my test, because the code itself will actually be in a new file.
[05:43] I'm going to call it add_test. It has the same requirements as our index test. I'm going to paste those in here. Then I'll start my describe block. This endpoint is actually going to have two different sets of tests.
[06:00] It'll accept the POST that has the numbers that it returns the sum of, but I'm also going to include a GET operation as well, that returns a JSON object with some instructions in case someone tries to execute a GET request against it.
[06:15] It's probably not a real common real-world scenario, but it's going to allow me to show you some things along the way. We've got our empty parameters here, our fat arrow curly braces, and then our IT statement.
[06:31] We've a done parameter, and we'll use chai-request again to start our server, do our GET request to the add endpoint, and then on end, have our error response objects. We'll start, just like we did. We'll say we should have a status of 200.
[06:51] Let's run that real quick. One test -- our previous test on the home page -- passed. We now have one failing test /add is not returning the usage instructions. Expected with status code of 200, but instead got a 404. That's because we haven't created it yet. Let's create it now.
[07:13] To add it, we'll go over to our app.js file. We'll say that add=require routes add. Then we'll see app.use/add route uses the add variable we defined. Then over in route, we'll create that file, add.js.
[07:35] Inside of here, we'll need to use Express, and we need our router. We can define the endpoint here. We'll just return an empty string. I'll export that, save it. If we open up our console again, can run a test with Mocha.
[07:55] It says that the timeout of two seconds was exceeded. That's because I didn't call our done callback parameter here. If I save that, rerun Mocha, both tests are passing. Sorry, test says that it returns usage instructions via JSON. We need to test for that.
[08:16] We can say our response body should have a property called message. If we run a test, clearly, we don't expect empty braces to have a property called message. Let's add that in. Instead of doing response.send, we can do response.json, and include or JSON object, run our test.
[08:38] Both are passing, but it doesn't have the instructions itself. It just has the objects. We can also test the contents of this, as well as saying .that=user post request. Again, we're expecting this test to fail because we haven't included that. It's exactly what our error message says.
[09:00] It got an empty string that it expected to say, "User post request." If we return to our add message and actually add in the text "User post request," save that. Our tests are once again passing.
[09:16] Now, it's time to write the next failing test of our endpoint, which is going to be for the POST function. To do that, I'm going to start a new describe block. We'll start with a simple test. It says it should return 5 when supplied with 2 and 3.
[09:33] Provide our callback parameter. Once again, we do a Chai request to our server. This time, we're going to do a POST to the add endpoint. Now, we have to do a little bit of setup. We're going to set our content type headers to application/json.
[09:51] Then we'll send var1 with a value of 2, and var2 with a value of 3. We'll end that with our callback. We can say our response body should have a property named result=5. This time, we won't forget our done parameter.
[10:13] If we run our test, we're expecting that to fail because nothing was returned. Let's write the code required to make that pass. We'll add our POST function here. The simplest way to get this to pass is just return exactly what the test is asking for.
[10:31] Let's try that and see if that's going to work. It does. All of our tests pass, but that's clearly not the functionality we want. That's the magic of testing. We can do different things to ensure that the function is behaving like it's supposed to, not that it's just returning the expected result.
[10:51] The way we're going to get around that is we're going to create a new it block here. I'm going to copy and paste this one, and we're going to change our numbers. We want it to return 10 whenever we supply the values 4 and 6.
[11:05] We'll actually supply those values. If we run our test again...We need to change the expected value to 10. Now it fails. It expected 5 to equal 10, which clearly, it does not. We need to modify our add function to reflect that.
[11:29] We can get rid of that line because it's not doing what we want. We can call var1, we'll get that out of the request body, do the same for var2. We'll set our content type to application.json, and we'll return that by returning a result variable with the sum of those two variables.
[11:49] Let's try our test again. Now, all of our tests are passing, but not only are they passing, but we're testing different scenarios to ensure that it's passing based on the right behavior, not based on the expected result.