SinonJS Fundamentals
-
The State of Testing at Globomantics
Welcome to Globomantics
Hi, and welcome to SinonJS Fundamentals. I'm Nate Taylor. This course covers what you need to know to be proficient in unit testing with Sinon.JS. I want to start by welcoming you to Globomantics. They're an industry leader in ecommerce. They have APIs and applications for just about everything from buying their products, adding new items to their inventory, and collecting payments. They also have APIs that are available for consumers to use, allowing their users to build up a home library as an example. Recently, Globomantics has been burned by some critical bugs. These bugs weren't caught before production because they didn't have a very good test suite to run before releases. As a result, the managers have dictated that there had to be an increase in unit testing. As one of Globomantics' newest employees, you've already pulled the code down for one of their APIs and you've heard a lot about writing tests from your coworker, so you decided to kick off the tests. As you do so, you see some failed tests. Unfortunately, the error messages don't provide sufficient information to be able to understand why they failed. For example, the first test tells you that it was expecting 4 books, but that it got undefined back. Without digging in, it's hard to know why it returned undefined, but one thing is sure, it's incredibly frustrating to have tests fail the first time you run them. As you start to dig in to the first test, you find this code block and see that, at least for the first test, it's trying to load all of the books from the database. In fact, all of the failing tests have some kind of database call. None of the getting started docs mentioned anything about setting up a database. From talking to your coworkers, you figured it out and you got your database up and running. After getting the database running, you kick off the test again. This time it's better, but only by a little bit. One test is now passing, but you still have two failing tests. This time though, the first test tells you that it was expecting 4 books to come back and actually 12 came back, and if you did a quick peek at the database, it would tell you that there are in fact 12 books in their table. The second test is telling you that you were expecting a 404 but got a 200. If you were to dig in, you'd probably find that they were passing in an ID that used to not exist and now it does. Now that you've made some progress, you're starting to understand some of Globomantics' testing issues. By depending on the database, the only way the tests run is if they can make a connection to the database, but not only that, even when it does connect to the database, the tests still can fail because the data in the database changes, but the tests assume that it will remain the same. If you've encountered issues like the ones Globomantics is experiencing, then Sinon.JS will be a great relief for you. It'll help you write tests that give you control over your data. You'll be able to verify the logic of your application, and you'll be able to simulate errors that would be difficult to do with actual systems.
-
Installing Prerequisites
Globomantics does a lot of JavaScript development, and this project is no different. Of course, you already knew that since the course is on Sinon.JS. Before you get the project downloaded and running, it's important to make sure you have the prerequisites installed. For this course, those are Git and Node.js. If you don't already have Git installed, start by going out to git-scm.com. This site will let you download Git, which you'll use later in the course to get the code or to change specific branches of the code. While the video shows grabbing Git for OS X, once you've downloaded Git, the default options should work whether you're in OS X or in Windows. Once Git is installed, it's time to install Node.js. Node will allow you to run a JavaScript server on your computer. This will be essential as Globomantics' API is written in JavaScript. Start by going out to nodejs.org. On the home screen there will be two buttons for you to choose from to download Node.js. I personally always go with the newest version, but the long-term support, or LTS version, is also a viable option. As with Git, whether you're in OS X or Windows, the default options when installing Node.js should be fine. Once those two items are downloaded and installed, open up a new terminal. To ensure that everything is installed correctly, run the following commands. Git --version will tell you the version of Git that is installed, node -v will tell you the version of Node that is installed, and npm -v will tell you the version of the Node package manager that was installed. You'll use npm later to install dependencies, including Sinon.JS. With these dependencies installed, you're ready to continue on by getting the source code down to your machine.
-
Getting the Sample Project
Now that you've got Git and Node installed, and you've seen a glimpse of how testing can be impacted by not using fakes and mocks, it's a good idea to get the project code. You can do this by going out to the following GitHub repo, github.com/taylonr/ps-sinonjs. On the right, there's a green button that says Clone or download. When you click that, it opens a modal with a URL for you to copy. On my screen, it's suggesting I use the SSH version because I've already connected my GitHub to my laptop using an SSH key. If you've not done that, make sure you use the HTTPS version. Copy this URL by clicking the icon on the right, and then go to a terminal. Go to a folder where you want to store this project, for example, home/code, and then run the command git clone, and then paste in the URL you just copied. This will pull all the code into a subfolder named ps-sinonjs. Once that's done, change into that directory, then run the command git checkout state-of-testing and hit Enter. This will change the branch to the state of testing, which is the same name as this module. Now that that's done, run npm install. This will download all the libraries this project depends upon. For the sake of brevity, I've sped up this portion of the video, but it shouldn't take too long for you. After it's done installing everything, run the command npm run start:dev. This will start a Node.js server and it will tell you that it's running on port 3000. If you open a browser and navigate to localhost:3000/landing, you should see a message stating that the sample API is up and running. At this point, you can kill that process by issuing a Ctrl+C in the terminal. Now if you run the command npm test, you'll see the failing tests from earlier in the course. All of this is good validation that you have, in fact, got the code up and running.
-
A Quick Tour
As was mentioned earlier in the course, one way to fix the broken tests was to spin up a database; however, you'll likely notice that at no point have you been told to download a database. That's because since the focus of the course is faking out requests using Sinon, you don't need and won't use any database in this course. Throughout the course, you'll update Globomantics tests and add new tests using Sinon, so you won't need any external systems. Before getting to that though, there's a couple more items to take care of. First, using your IDE of choice, open up the folder that you just created. I'll be using Visual Studio Code, but any IDE, or even an editor like Sublime will work. Once you open the editor, you'll see a list of files and folders. These are the files in the project that you'll be interacting with in this course, and they'll reside underneath the server folder. Underneath that folder are quite a few other folders. There's things like models that store the various database models that the API deals with, there's a folder for seeders and one for migrations that help seed and migrate the Globomantics database when it's first stood up, but one of the main folders you'll be working with, at least initially, is the controllers folder. Inside that folder are the controllers for the API, as well as the spec files for the controllers. For example, there's a book.controller and a book.controller.spec file. You'll get more exposure to these files throughout the rest of the course. The final file to look at is in the root of the project, and that's the package.json file. Once you open that up, you can scan both the dependencies and the devDependencies. You'll see mocha and chai, which are used for testing, but you won't see sinon. That's because Globomantics isn't yet using Sinon. You're going to have to introduce it to them. So go back to the terminal, or if you're using Visual Studio Code you can use the integrated terminal, and run the command npm i -D sinon. I is a shortcut for install, and the -D is a shortcut for save dev. You're telling npm to save this dependency in the devDependencies section of package.json. Sinon is the name of the package that you're installing. Hit Enter, and it'll go out and fetch Sinon.JS from the npm repository and install it into your project and save it in your package.json file. Once this is all done, you're ready to start tackling mocking, faking, and spying on the Globomantics application.
-
Path Ahead
Throughout the rest of the course, you'll learn about how and when to use Sinon.JS for various types of testing functionality. The goal is to get you equipped to handle the various hard parts of testing, such as how do you write a unit test that needs to make an AJAX call? You'll be doing this using the Globomantics book API. This API has a list of books that you can buy at Globomantics. It then allows users to put specific books on a wish list, or to include specific users in a household and combine all of their wish lists. In the module Spying on Functions, you'll start with the most basic aspect of Sinon, spies. Essentially, spies are a type of fake that tell you if a function was called or not. Sometimes you need to know more than just if a function was called, and you'll want to control how that function responds when it is called. In Sinon, those are stubs, and you'll tackle stubs in the module Swapping a Stub for a Function. Mocks combine some of the functionality of stubs and spies and allow you to verify that specific behavior has happened, and you'll learn more about them in the Verifying Behavior with Mocks module. By this point in the course, you'll be pretty well versed in the various ways to fake out code. The module Controlling How Patterns Are Matched, is a bit of a break from the various ways to fake out a function, and instead covers ways that Sinon can help you verify that what you thought was going to happen actually happened. The module Faking out XHR and Timer Calls will show you how Sinon can greatly simplify one of the hard problems of testing. For example, faking out the AJAX call. Once that's done, you'll learn how you can set up and tear down Sinon in a more efficient manner in the Easier Set Up and Clean up with Sandboxes module. Finally, the course will wrap up by offering you some next steps that you can take sign Sinon including other libraries that you might find helpful. I hope you're ready to get into the weeds of Sinon.JS.
-
Spying on Functions
Spying on Functions
You've got the prereqs installed, you've had a chance to get a quick glance at the code, and you've even run the tests and you've seen how they're failing when you're not connected to a database. At this point, I'm sure you're ready to get to work and begin figuring out how Sinon can help both you and Globomantics out. The first part of Sinon to examine is a spy.
-
What Are Spies?
Spies are the basic building block for Sinon. That is, a spy is the simplest thing that Sinon does, but the fact that it's simple doesn't mean that it's trivial. Before building on this foundational component, it's important to understand what a spy is. A spy, sometimes called a test spy, has its name because it parallels a spy in the real world. That is, a test spy observes a given function, it watches the function closely so that it can learn as much as possible about it. Some of the things that a spy can learn about a function are how many times that it was called, or what parameters, if any, were sent to it, or what it returned, or even, did it throw any exceptions? When looking at this list, it's important to note that the things that a spy can observe are external to the function. That is, the spy is not monitoring anything going on inside the function. To a spy, the function is a black box. In some ways, a Sinon spy is like your nosy neighbor. They see if you leave for work in the morning, and they see you when you get back from Globomantics at night. They can tell what time the lights go off at your house, and they might even be able to see you playing with your kids in your backyard, but once you go inside, they can't tell what's going on in the house. This is how Sinon spies operate. It watches your function and tells you all sorts of useful and interesting pieces of data about how your function was used. To get started with spies, you first need to be able to create a spy. There are two different ways that you can create a spy using Sinon. The first way is an anonymous spy. This would be created simply using something like what you see on the screen, const mySpy = sinon.spy. Here, your variable, mySpy, now has all the properties and functions of a spy. You could pass this spy around and gather intelligence about what was going on in your code. This is particularly useful when interacting with functions that take callbacks. A common JavaScript pattern, particularly in legacy JavaScript, is to have a function that accepts a callback. For example, the following function will alert the user by calling the function that was passed in. While this function is contrived, it would allow you to pass in a function like console.log, window.alert, or even an audio API that spoke the message. By using Sinon, you could pass a spy in for that callback. So here you have const mySpy = sinon.spy, and then you pass that in to your alert that says I'm watching a Sinon course on Pluralsight, and you pass in your spy. By passing in mySpy, you're able to learn more about how the callback operated and verify its functionality in an automated test. This wouldn't be so easy if you passed in something like console.log. While the anonymous spy is useful, there will be other times when you want to spy on an existing function. Sinon can help out here as well. If you passed in a module name and a function name to the spy, you will replace the function on that object with a spy. For example, the Globomantics book API has a concept of wish lists. These objects can have items added by calling wishlist.addItem, or they can have items removed by calling wishlist.removeItem. So the structure of the Wishlist is what you see on the screen. For your test, you want to spy on the addItem. You can create a spy for that function by wrapping it with Sinon. The syntax would be like this, const addItemSpy = sinon.spy, and then the first parameter is the Wishlist object, and the second parameter is the name of the function, addItem, in quotes. At this point, wishlist.addItem is no longer the function you defined in your code base, instead it's a spy, but if you were to look at the structure of the wishlist, you would see something like this where addItem is a sinon.spy, but removeItem is still the function that you had specified on Wishlist. If you were dig in just a little bit more, you'd see that the addItem is a function that returns an object, a sinon.spy, that has several other functions. Here is a partial output of what that sinon.spy would look like. There's a resetHistory, invoke, named, getCall, getCalls, and several other functions that would scroll for a couple of pages. The fact of the matter is, whether you create a spy using the anonymous method, or by passing in a module and function name, the resultant object is the same. Both will have the functions like getCall or getCalledBefore. These are the functions that allow the spy to tell you about all the things it learned while being nosy.
-
Verifying a Spy Was Called
Knowing how to create a spy is essential to using the spies, but it's not sufficient. In other words, now that you have a bunch of created spies laying around, how can you use them to your advantage? The simplest way to use a spy is to check if the function that was spied on was called. This would be accomplished by using some of the properties on the object returned by the spy. Earlier in this module, you saw how to create an anonymous spy named mySpy. Using that same setup here, you're now passing in mySpy to the callback of the alert function you've already created. You could test that your alert was calling the callback by having the following assertion in your code, expect(mySpy.called).to.be.true. The assertion here is using the expect flavor of the Chai assertion library for Mocha.js. If you're not already familiar, a quick overview of what's happening will probably help. So here, expect is a function. The one argument passed in is the result that you want to check. After the closing parentheses is a fluent syntax to build out what you're expecting the result to be. In this case, it's expecting it to be true. There are other assertions that can be made, and you'll see some of those throughout the rest of the course. Returning to the Sinon portion of the assertion, you see a new property, and that is simply called. This property stores whether or not the spy has been called. If it has, it'll return true, and if it hasn't, it'll return false. In the example here, you'd expect alert to call the callback. In this case, that's your mySpy. For the purpose of testing alert, you don't actually care what mySpy does once it gets called. That's the responsibility of other tests. The mindset here is that if alert always calls the callback, then you know that it's working correctly. If at some point you're not seeing or hearing your alert, then that's almost guaranteed to be a problem with the callback that was passed in and not the alert function itself. Testing in this way, called unit testing, helps isolate your code so that you're able to break up the components and test them in isolation. Additionally, it gives you confidence that alert will allow you to pass in a variety of callbacks, as long as it takes the message parameter that alert passes in.
-
Demo: Verify a Spy Was Called
Just by learning about how to verify that a spy was called, you're now ready to go fix Globomantics' first failing test. Before jumping into the code, run the tests again to be reminded of the errors that are happening. When you run the test, you'll see this test failing, Books controller When creating a book Should add the book to the database, and it failed with TypeError: Cannot read property title of undefined. The error occurs because you're trying to access a property on an undefined object. The object is undefined because when it tried to go out to the database it failed since there is no database. To solve this, open up your IDE and load the file book.controller.spec.js. On or around line 19, you'll see the test named When creating a book. This is the test that's failing, and there's a couple things going on already in this test that need a quick explanation. First, there's the module named httpMocks. This is a package that is located in your package.json file, and it fakes out making HTTP calls. Globomantics used this in their testing so that they could easily access the controller functions. For example, they can simulate a post call by calling controller.create, which you see on line 31. In this test, the req, or request parameter, is going to be a mocked request object, and the test specifies what the body is. It's a book defined a couple of lines above. Then it creates a fake res, or response object. This ensures that the response object passed in to create has all the right properties and functions that would be necessary for the Globomantics express app to complete their controller action. If you were to trace through this code, you would see that the book.controller.spec is using the book.controller, and that book.controller is really just an implementation of the crud.controller, and that the crud.controller simply uses a crud object to make its calls. And so if you were to go all the way down to the crud object, you would see that there is a create function, and that create function depends on the model, and the model calls create. For the books controller, that model is the books model. So if you go back to your test, you'll notice that the model is not used anywhere in that test. So the first step is to add that model at the top of the test file. You'll also need to bring in Sinon, so add the line sinon = require(sinon). Now you have the model that you want to spy on because this model is what's actually making the db calls. So go back to your test, When creating a book, and after the request and response objects, create a new spy that spies on the create function of the model. You do this not with an anonymous spy, but with the module spy syntax. Next, you need to change the assertion. Currently, the controller makes the create call and waits for a successful call, and then checks that the title is what you expected. This time, change line 36 to make the assertion that the model.create function was actually called. Now if you save your test and run your suite again, your new test should be passing, and you can see that because there's only 2 failing tests, whereas before there were 3. What does this passing test tell you about the Globomantics code? Well, it tells you that when controller.create function is called, it's ultimately going to go call out to the database. At this point, you don't really care what the model.create function does because it's an external library that you're using. So you're going to just trust that once the library gets your object, it's going to know what to do with it, and it'll perform that action correctly. You've now created your first spy and verified that it's working as expected.
-
Testing Pro-tip
Before looking into more detail about spies, I want to take a brief moment to talk about testing philosophy. When fixing your first test, you saw that while your test was exercising BookController's create method, what it really was doing was checking that the crud.js module's create method worked. A better way would be to write tests for the crud.js module. After all, if crud.js works for the BookController, it would also work for the Wishlist controller; however, as you've already learned, Globomantics doesn't have great testing practices. As a result, they wanted to start at the controller level. This is fairly common, particularly in projects where tests are added after the fact. In Globomantics' case, these are tests that were written only after their API experienced production problems. What I would typically do is start with tests against crud.js, but I wanted you to be more exposed to real-world situations. So if this was a code project that I encountered in the wild, I would likely start by fixing the book.controller test first, like you just did. I would make sure that I could get them all passing, and then only after that would I move to crud.js. Then I would only include tests in the BookController test suite that were unique to BookController. Either way, Sinon would behave the same. So the test suite will not impact your ability to learn Sinon.JS; however, I think it's always good to be cognizant of various ways of structuring tests.
-
Verify Multiple Calls
In the process of cleaning up Globomantics' first failing test, you saw that you could verify that it was called. Sometimes that's enough, but other times you want to know about multiple calls. Sinon can provide you that information as well. Consider the example of when a user logs in to an ecommerce application. The app might need to fetch their preferences, order history, and available products. In order to test this initializeNewUser function, it would be helpful to have a spy on your db.all function. You could have something like this where you say const get = sinon.spy (db, all). And you might write your first test for this function similar to the test that you wrote for Globomantics. That is, you can have an assertion like expect(db.all.called).to.be.true, and this test passes and you go on about your day. Later though, you discover your application is never fetching the list of available products. How can this be true if your test is actually still passing though? You're checking that it's called, but it's not. Your test is passing because the all function does in fact get called. It's called when you fetch your user's preferences, but after that, there's an error and so your app never fetches the order history nor available products. If you want to make sure that it did make three separate db.all requests, you could use a different property on the spy. Your test could then be changed to this, (db.all.calledThrice).to.be.true. This property is only true if the spy was called exactly three times. There's similar properties for calledOnce and for calledTwice. The called property will be true as long as the spy was called at least once, whereas calledOnce will only be true if it's called exactly one time. There might be other times where for some reason you need to check that a spy was called more than three times. There's no called four times property. Instead, there is a callCount property that will hold the number of times a spy was called. So if you wanted to verify that your function was called four times, you could have a line like what's on the screen, expect(db.all.callCount).to.equal(4). By using either the calledOnce style properties or the callCount property, you're able to verify that a function that is spied on is called the correct number of times, much like a nosy neighbor can tell you just how many times you pulled in or out of the driveway yesterday.
-
Examining Parameters Passed to a Function
Sometimes when you're writing tests, it's not enough to know that a function was called, or even that it was called a certain number of times. Instead, you need to know that it was called under certain conditions, and Sinon provides a way to spy on that as well. You just learned about checking that a spy was called a certain number of times by using spy.callCount. In that example, your app was making three separate all calls to the database. As long as the get function was called exactly three times, your test would pass. After some changes were made to the initializeNewUser function, the test was still passing, but you noticed in the app that you weren't seeing any available products. As you trace out the calls, you notice that there was no all request to fetch available products. So how was the test still passing? Well, oddly enough, the changes introduced above in which the user's preferences were loaded once and the previous orders were loaded twice, there were still three total calls to db.all, but they weren't the ones that you were expecting. As you go back to the test, you decide to make the assertions even more specific. What you actually want to check is that there was one call to all, using preferences, one call to all using history, and one call to all using products. A spy accomplishes this not by only monitoring the function, but also tracking the arguments passed to that function. Your setup would not need to change. So you could still do simply sinon.spy.db.all, but then for the various tests you could use the withArgs function like you see on the screen. Expect(db.all.withArgs(preferences).calledOnce).to.be.true. You could then repeat this process for the previous orders and also available products. If you were to do that, you would in fact see two failing tests. The first would fail because previous orders was called 2 times not 1, and the second would fail because available products was called 0 times instead of 1. This level of specificity really helps you, as a developer, see more clearly the problem with your code. It would've let you catch this weird case while running your unit tests instead of needing to load up the application in the browser and check it out. The signature for withArgs takes one or more arguments, and it'll match on all the arguments specified in the order that they're specified. For example, if you had a function that took several different strings and would concatenate them, you might call it like this, concat(Charles, Spurgeon), concat(Charles, Wesley). You could then check on your spy with expect(concat.withArgs(Charles) .called).to.be.true. Even though both times you called it with more than just Charles, this test still passes because you're telling your test you only care about the first parameter that it was called with each time. There's other functions on a spy that utilize a similar philosophy with checking arguments. For example, there's a function called calledWith that accepts a list of args that'll return true if the spy was called with those arguments. There's callOnceWith, and that'll check if the spy was called exactly one time with those arguments. There's an alwaysCalledWith that returns true if, and only if, every time the spy was called it had these exact arguments specified. If you want to be more precise with your arguments, you can tell Sinon that you want to check that the spy was calledWithExactly. Using this function and the assertion from the previous example would fail. So if you had this test of expect(concat.calledWithExactly(Charles)) .to.be.true, that test would fail because concat was never called with only one parameter, and so it would never match that it was called exactly with Charles. You can also check that a spy was alwaysCalledWithExactly, which combines both the always functionality and the exactly functionality. Sometimes it's helpful to check that a spy was not called with a specific parameter. For example, if you were to copy an object and then save it to the database, you might to verify that you didn't call save with the original object's ID. You could use neverCalledWith to make sure that your function was never called with the arguments specified. All of these functions return either a true or false, and you can trigger off of that to know if the function was ever called in that way; however, it can quickly become frustrating when a test fails and all you see is something like this, AssertionError: expected false to be true. It tells you that the call you thought would happen didn't happen, but it doesn't give you any insight into why it didn't happen. This becomes particularly frustrating once you revisit the test you wrote for Globomantics. If you tried to make it more specific and had the assertion be expect(model.create.calledWith, and you specify the title, the author, the publication, and the isbn, and you said expect that to be true and the test failed with expected true but got false, you wouldn't know why. Did it not make the call? Is one of the arguments wrong? And if it's one of the arguments that's wrong, which one is it? Did it make the call, but with the wrong author? Did it make the call but with the wrong publication date? Well thankfully, Sinon lets you solve that relatively easily. Since it's already monitoring all the arguments passed in, you can access the individual arguments as well with the args property. The args property is a two-dimensional array. The first dimension is each call to the function, and the second array is each parameter for the specified call. That sounds a little bit more confusing than it really is. Take a look at the concat example from earlier. We have concat(Charles, Spurgeon) and concat(Charles, Wesley). Then you could do expect(concat.args 0 1).to.equal(Spurgeon). That's because args 0 is going to get the Charles Spurgeon call, the top call on the screen, and the args 0 1 is going to get the second argument made on the first call, or Spurgeon in this case. So if this test were to ever fail, it would tell you that it expected something else to equal Spurgeon and now you have a little bit more clarity of why the test is failing. Sinon also provides some syntactic sugar to make working with args a bit easier. It provides a function named getCall. This will get you the details about the call you specify, and just like in the array, it's 0 based. One of the properties it will return is args, but now args is scoped just to this call. So you could rewrite the previous expect statement as expect concat.getCall(0).args1.to.equal(Spurgeon). Now if this call fails, you'll get a much more explicit error message. For example, if you did expect concat.getCall0.args1.to.equal(Jones), you would get the assertion that expected Spurgeon to equal Jones. And this tells you specifically why the test failed. It's not that the function didn't get called, but rather, it didn't get called with the expected parameters. By being able to monitor not just that a function was called, but what it was called with, Sinon is able to help you be more specific in your tests. And specificity in your tests will often pay off when the test fails because it will guide you to a more clear answer of what went wrong.
-
Demo: Verify Right Book Was Created
Return now to your IDE in the first test that you cleaned up. If you remember back to the original test, it was expecting that the data that was returned from the controller had the correct title. In an effort to make the test pass, you took a simpler route and simply made sure that the create call occurred. It was arguably better than what was there because at least now this test could be run even without a database, but the downside is that if this code ever got changed to model.create with no arguments, the test would still pass; however, you can actually have a very similar assertion to the original that checks the title. So replace line 36 with one of the examples from earlier in the course. Here you're checking that model.create.args0 0.title).to.equal(Test Book). So this spy is on the create function and gets the model passed in, calls 0 arguments 0, and since it knows it's an object, it can access the title property on that book and verify that it's equal to Test Book. When it fails, it can fail in one of several ways. It could say that it expected undefined to equal Test Book, and that would at least tell you that the title parameter was not set on the object passed in. It could also tell you that it cannot get the title of an object undefined, which would mean that there was no argument 0 on call 0, so someone screwed up and probably changed it to the createEmptyCall, or it could even tell you that it can't read property 0 of undefined, which would mean that the function was never even called, and so there can't be an argument 0 on a call that didn't exist. All of this is much more information than you would have received if you had simple stayed with expect model.create.called to be true.
-
Verify Exceptions
One of the great advantages of unit testing is that you can easily force your application into an error state, and that can sometimes be difficult in the normal usage of your application, but if you ever want to verify that it behaved correctly in error situations, Sinon can help with that. The normal execution path of a function is that it returns a value, but sometimes it might also throw an exception. For example, if you tried to access a file that didn't exist, it would make sense to throw an exception. With Sinon, you're able to verify that your function threw an exception, provided you set your test up correctly. Imagine you have a function addToList on your wishlist module and that it looks like this. This function would check that the list you supplied is defined before trying to add to it. If it's not defined, it would throw an error. Well, how could you go about testing this? Much like the args parameter, Sinon keeps track of any and all exceptions that are thrown. It also provides some syntactic sugar to interact with them. As a result, you could write a test like this where you have Wishlist.addToList, and you're passing it null, and then the object you want to add to that list. And then your expect statement is expect(spy.threw).to.be.true. This would execute the function, catch and swallow the exception, and then let you verify that it was thrown with your assertion. In addition to the empty function threw, you can also check threw(TypeError) to check that it threw an error of whatever type that you specify, at least once, threw(obj) to check that the given object was thrown at least once, and much like the call checks, you can add an always. For example, alwaysThrew(obj) to verify that the function always threw an error. With these functions, you can now test out even the error paths of your code to ensure that they're working as expected.
-
Spies as Building Blocks
You've now gotten a quick taste of how spies can be helpful when writing tests. They give you the visibility into how a function is called, and if you're focusing on unit testing, oftentimes all you care about is that a function was called. For example, in Redux applications, I will often test that my actions are sending off the right of it after a successful update has occurred. As far as my action is concerned, it doesn't care what the event does, it just needs to ensure that it was fired. So that would be a good candidate for a spy because I can check that the correct event occurred and that it received the correct information from my action. I tend to favor checking args instead of just verifying that a spy was called. I've been hurt way too many times by only checking that something was called when what I really wanted to know was, was it called with the expected parameters? It's sort of like the nosy neighbor mentioned earlier in the module. The neighbor isn't super interested in how many cars came to or left you house, but they are super interested in how many times a car that you were riding in came to or left your house. Whether you choose to use the called, calledCount or args to check that your function is behaving correctly, you now have experience with the basic building block of Sinon. At this point, you've taken Globomantics' test code base about as far as you can with just spies. Coming up, you'll learn about how you can clean up more of their failing tests with other Sinon functionality. While the API of these new pieces will be different, the concepts and philosophy will build off of what you've already learned from spies.
-
Swapping a Stub for a Function
Swapping a Stub for a Function
You've already started cleaning up some of Globomantics' broken tests using spies. Unfortunately, you were only able to clean up certain tests. For example, tests that wanted to verify a specific function was called. In order to get more tests fixed, you need to control what data is returned from the database. That's just part of what you'll learn as you start diving into stubs in this module.
-
What Is a Stub?
When looking at the test ladder in Sinon, stubs are one rung higher than spies. Sinon.JS's documentation defines stubs in this way, test stubs are functions, or spies, with pre-programmed behavior. There are two key points to this definition. First of all, stubs are spies. This means that a stub has access to the entire spy API, much of what you've already learned about. The second key point is that stubs come with pre-programmed behavior. Spies were concerned only with monitoring behavior. That is, they could tell you about a called function, but stubs are different. Stubs actually define the desired behavior for a function. Where spies act like your nosy neighbor monitoring you when you come home or leave, stubs are more like an actor wearing a mask. You can talk to the person wearing the mask, but when they answer they answer in character. The mask gives the actor control over what you experience. With tests, it allows you, the developer, to control the behavior and specify what should happen whenever a function is called. Simply put, stubs give you control over what data gets returned, and whether that's some kind of object, or an error, Sinon puts you in the driver seat. Much like spies, a stub can be created in a variety of ways. You can create an anonymous stub by simply doing const myStub = sinon.stub, or you can stub out a specific function on your module using something like this, sinon.stub, and then you pass the module, in this case Wishlist, and the name of the function in quotes, in this case addItem. And just like you saw with spies, the addItem function on the Wishlist object will now have a Sinon function with various functions and properties on it. There's one more way that you can create stubs, and this only works for stubs, not spies. You could call sinon.stub and then pass in the Wishlist module. This approach will replace all of the functions on Wishlist with a stub. With this last approach, Wishlist would look like this, addItem would be a Sinon.stub, and removeItem would be a sinon.stub. While this is possible, it should be used sparingly because it will mock out the entire module. Most of the time it's better to only stub out specific methods. This gives you more control over what's going on with your tests. Additionally, if you stub all the functions on an object, it's possible that you'll need to perform more set up to get your test running. For example, you might need to make one stub call another, and then that can get kind of messy. Before continuing on, it's good to take time to talk about when you would use a stub. There's two major reasons. The first is that you want to control the execution. If you had the following code where you have a function that happens with a rare exception before you save to the database, it could be hard to produce this rare exception in the function, or it could require a lot of set up. For example, it could require several different objects to each have a specific state. By stubbing out this function though, you could now test that when it throws an exception, the database save function will not be called. The other major reason to stub out a function is to prevent any undesirable behavior. Remember that a stub has predefined behavior. So if you had a function that made a payment via a payment gateway, as an example, you would likely not want to hit that function thousands of times a day as you run your tests, regardless of the reason why you would want to stub out a function. The remainder of the module will show you the various ways that you can stub out a function.
-
Returning Fake Values
Since stubs allow you to be in control of what gets returned, the most basic stub will simply return the value that you tell it to. Based on the syntax that you learned in spying on functions, you might already have a guess for how to return a value on a stub. It's simply .returns. For example, by adding a .returns to an anonymous stub, you can control what's returned. So const Pluralsight = sinon.stub .returns(Pluralsight) means that whenever you call the Pluralsight function, it will return the string Pluralsight. This returns will return whatever parameter is passed to it. As you just saw, it could return a single string, such as Pluralsight, or you could have it return an array, or an object, or even a function. This gives you the power to stub out any function in your application no matter what it's returning. Sometimes though, you'll need something a bit more complex than always returning the same value. Sinon provides this as well. In the previous module, you learned that you could use withArgs to check that a spy was called with certain parameters. With stubs, you can also withArgs, but you don't have to wait until the assertion phase. Instead, you can use this function to tell Sinon when to return specific values. With the code on the screen, if the code calls myStub(string), it'll find the matching return value and return it. If you called myStub(number), it'll return the number 1. Additionally, if you were to call myStub with the parameter bigNumber, it would return undefined. This is because Sinon is unable to find any specific return that matches the parameter you passed in because you've not specified a return for the bigNumber argument. I will often take advantage of this behavior to ensure that only the test data I set up will be used. By using withArgs, I can ensure that only the calls that match my setup will return data. A more generic returns might allow some false positives. Similar to withArgs, you can also control when the stub returns a value based on the number of times it's called. This can be useful if the stub is used inside of a loop or a complex workflow. You can do this by using onCall and pass in the call that you care about. Similar to the getCall function on spies, the callCount is 0 based. Based on how the code is configured on the screen, the first call will return true, the second call will return false, and the third call returns maybe. The fourth call also returns maybe. In fact, all subsequent calls to myStub will return maybe because this value is the value that was defined without any arguments. Since Sinon.JS uses a fluent syntax, you can actually combine functions to handle even more complex cases. For example, you could combine the withArgs and the onCall function to control exactly what gets returned and when. Here's an example that tells Sinon to return false on all calls, except on the second time that it's called with the number 1, and in that case return true. So the first time it's called, it returns false, the second time it also returns false, the third time as well, but this is the first time it's called with the parameter 1. Finally on the fourth call, it has finally matched the setup. That is, the fourth call is the only call that will return true because the fourth call is the second time it's been called with a parameter of 1. As you can tell, you have several different ways of controlling when a value gets returned from a stub. You can be as precise or as general as you want when telling Sinon how to handle your return values.
-
Handling Promises
As you just saw, there are lots of ways to return a simple value, but JavaScript functions often need to do more than return a simple value. Sometimes you'll need a way to stub out a promise. If you tried to go fix Globomantics' broken tests after the last clip, you would have likely run into some issues. From looking at the code, one function you'd want to stub out is the model all. So you might try something at the top of the screen. Sinon.stub(model, all).returns, and then you give it an array with a single object of id: 1. As you just learned, this would cause every call of model.all to return an array with a single object id of 1; however, when you tried to run this you'd quickly encounter an error. Your test would still fail, but the message would now say TypeError: model.all.then is not a function. This would happen because the system under test was expecting a promise, but you only returned an array. You could try to return your own promise object with something like what's on the screen, sinon.stub(model, all).returns(new Promise), and then you give it an empty resolve reject function, but the problem is that your test would now fail with the error Timeout of 2000ms exceeded. For async tests and hooks, ensure that done is called; if returning a Promise, ensure it resolves. The last line there is truly the source of the problem. You've correctly returned a promise with the stub that you see on the screen, but you don't have any way to tell it to resolve. So your test times out waiting for it to resolve. Thankfully, Sinon anticipated this need and provides a way to solve it. Instead of a normal .returns function, there's a .resolves function. Whatever value you pass in will be the value sent to the then function. You would then set up the function the same as if you were using returns. So like you see on the screen, sinon.stub(Wishlist, addItem).resolves id of 1. This property stubs out the promise in your code, like what you on the screen, and then the value of data here is the same as what you passed into resolves. With this, you're no longer constrained by promises in your code. Additionally, Sinon doesn't just consider the positive case where a promise resolves, but it can also handle the case where you need a promise to fail. After all, if you're using unit testing to be able to simulate errors with some of the systems you've connected to, you'll need to be able to cause errors to occur as well. It accomplishes this with the .rejects function. There's actually two main ways that you can reject a promise with Sinon. The first method with an empty argument list to rejects will simple reject with an error. The second is more useful in my opinion, and that takes a value and passes that to the catch function. The reason I find this more useful is that in most of the applications I've worked on, when a promise is rejected there is an error object, and this allows me to simulate that object. As you've seen elsewhere in the course, Sinon does a pretty good job of allowing you to be able to combine function calls and chain them together to produce the desired result. This is no less true with .resolves and .rejects, so you can still set up complex logic on your stub. For example, the code on the screen stubs out that only when you pass in the arguments id of 1 will it reject; otherwise, it'll resolve. With this, you see that you can use multiple functions to make your resolve or rejection much more specific.
-
Demo: Stubbing Resolved Values
Now that you have some understanding of stubs, perhaps it's time to revisit Globomantics' code and see if you can clean up any more of the tests. If you go back to your shell and run your tests, you see that you have two broken tests. The first one is that Books controller When getting a list of books Should return 4 books, and it gives you the AssertionError: expected undefined to deeply equal 4. The second failing test is Books controller When getting a specific book and the book does not exist Should return a 404, and it gives you the AssertionError that it expected 500 to deeply equal 404. Without looking at the code for the first test, you might be able to guess what's happened based on the description. It states that you were getting a list of books and you expected 4 books, but you got undefined. If you open up crud.js and look for this list function, it should be around line 53, here you can see that it performed a model.all, which tried to go get all the books from the database. And then, on the next line it calls repsonses.ok and it passes in the data that it got, which will take the values returned from the database and send it out as a response from the API. Your assumption that it was expecting four books back from the database has now been verified, but how can you fix this using stubs? Well if you go into the book.controller.spec file, the very first test is the one that states _____ When getting a list of books. _____ using what you've learned so far _____ .resolves. The easiest way to fix this is to stub out _____. So on line 12, enter a new line and create sinon.stub model, and tell it the function name all. Then you want to tell that stub to resolve, and you want it to be an array that has four objects. For the purpose of this test, the four objects can be empty because it's only checking that the count is the same. So save that file and return to the terminal. You now only have one failing test. This one references getting a specific book. It was expecting a 404, but instead got a 500 response code. The 500 is caused because it can't reach the database, so the server errors out and returns a 500; however, if you remember from earlier in the course, when the database was connected, this test still failed with a 200 instead of a 404. Go back to the book.controller and go down to the last test in the file. On line 48, you can see the specific book that this test is trying to fetch. You can do a little bit of code archeology here and note that when the test was written, they likely only had five or six books in the database, but now they have several more books, and so getting a book with id no longer returns 404, instead it would return 200, if the database is connected, or it would return 500 if the database isn't connected. So starting on line 52, create a new line and create a stub named find. This tells Sinon that you're going to stub out the findById on the model, which is the function that the getById controller code is using. Next, you want to configure that stub only in the case where the ID of 7 is passed in. Here you're going to have it return null to simulate not being able to find it in the database. So again, save this test and return again to the shell, and now you see that you have 3 passing tests. So at this point, you've now fixed the two tests that were returning specific data from the database, and you start to feel good that you're making a difference in Globomantics' code base. So you think, let me add one more test, and so you realize that there isn't a case yet for what happens when the book is present. So jump back into books.controller.spec, and below this one where it's getting the book does not exist, create a new describe block and call this describe block and the book does exist. Then, add a new it block for the test. Tell it simply that it should return 200. This test is going to be really similar to the one that you just tested with the 404. So you can actually start by copying the contents of that test. After you paste it in, there's a couple changes you want to make. So instead of the find.withArgs(7).resolves(null) on line 74, simply change it to resolves and give it an object. Then go into your assertion and change it from 404 to 200. This test should pass because you're telling it to give back an object whenever the findById function is called. If you save your test and then go back to your shell, you notice that instead of the test passing it's failing, and you get this error, TypeError: Attempted to wrap findById which is already wrapped. The next clip will show you why that error happened and how to resolve it.
-
Cleaning up After a Stub
You just wrote what you thought would be a pretty straightforward test, but unfortunately it failed with a weird error message. You're now ready to figure out what caused it and how to resolve it. Earlier when you were first learning about stubs, you saw this slide. It showed what it looked like to stub out the Wishlist module. It changed addItem and removeItem from their native functions to a stub function. And as you've been learning, stubs are functions with preprogrammed behavior. That is, Wishlist has now changed, not just in your function, but whenever Wishlist is used it will now have a stub for addItem or removeItem. This good news comes with a cost. It does allow you to be able to specifically configure a function, but while Sinon is helpful, it only knows what you tell it. In your first test, you have stubbed out the findById function and then tested whatever you wanted. Then in the second test, you did the same thing, but this time you're going to change the behavior from rejecting to resolving the promise. This is where Sinon gets confused. Which behavior should it allow? Remember, you're changing the module globally, so Sinon doesn't want to make that decision. Instead, it wants you to make that decision. After all, you're the one writing the test, so you probably know best. This is why you get the error TypeError: Attempted to wrap findById which is already wrapped. You have already made a findById stub, and you tried to make it a stub again. It's also why you created several stubs, but didn't see this error until you tried to stub the exact same function multiple times. So how can you resolve this issue? You need a way to clear out the stub and let it go back to the unwrapped state. With Sinon.JS this is called restore. Since model.findById is now a stub, it now has a new function on it named restore. So you can call it on the function that you're stubbing like you see in the second line on the screen there, model.findById.restore. It's a good idea to clean up your stubs in afterEach. This function will be called after every single test, or it block, regardless of if the test passes or fails, or even if it throws an exception. If you try to restore the mock somewhere else, oftentimes a failing test will prevent the restore from happening, and so you'll get this attempted to wrap function again, even though you see restore in your code because it occurs after an error happened. Additionally, it's important to ensure that you are calling restore on all stubs. If you don't, then you might experience some unexpected behavior. For example, you stub out Wishlist.addItem in one test, and then later are trying to test Wishlist.addItem. If you haven't restored it, it will still be using the stub. You've now got experience creating multiple stubs and should have a decent understanding of why you need to restore each and every stub that you use.
-
Additional Return Options
You've already seen how to return strings, arrays, objects, and functions from a stub. You've also learned how to stub out promises with Sinon.JS, but every once in a while you need slightly more control over what gets returned. Much like rejecting a promise, sometimes you want to simulate an exceptional case. You can accomplish this by using the .throws function. The code you see on the screen configures Wishlist.addItem to throw an exception with a message of Database disconnected every time that function is called. This goes back to the first reason to use a stub, to control the flow of the application. Using .throws, you can now easily simulate error conditions in your application. While onCall and withArgs are great aids to helping you narrow down specific cases of a stub, occasionally you need even more control. Sinon anticipated this and provides a callsFake function that you can apply to a stub. An example can be seen on the screen. Here, there's a module named utils, and it has a function named concat. What you want is any time that stub is called is to return a string of concatenated strings. You can add the .callsFake and have it accept parameters and the stub will call the fake function instead of the original. One note of caution though, since you are creating code to fake out existing code, it's possible that your fake function might have an error in it. As a result, I use callsFake very sparingly. I prefer other return methods for stubs that I need to mock out. If you find yourself constantly setting up the same returns, you might want to add that to the Sinon library and be able to use it on any stub that you have. You could create a pull request since Sinon is open sourced, but it's not likely that they'll take the code that only solves your problem. Instead though, they allow you to extend Sinon using addBehavior. For example, imagine you wanted to debug your tests, specifically you want to see when your stubbed function is getting called. You could define a new behavior log. This takes a message and then outputs the result of the trace, which shows the call stack, including line numbers. You could define this behavior in your code like you see on the screen. Then when you want to use it, you could attach it to a stub just like any other return function. Here sinon.stub(Wishlist, addItem).log(Called here). When Wishlist.addItem is called, you'd get a stack trace similar to what you see on the screen. Note the bolded line where it shows the specific line number where that function was called from. Whether you ever need to use these last two functions, callsFake or addBehavior, will be up to you and your team, but one of the takeaways here is that Sinon gives you the power to test your code in the way that does make sense for you and your team.
-
Spies vs. Stubs
Earlier in the course, you learned about the bottom rung of Sinon.JS, the spy. Now you've learned about the next rung, stubs. It'd be a good idea to take a minute to look at how these two types of test doubles relate to each other. One of the biggest and most notable differences is that spies simply monitor behavior. They will track what gets called and what gets passed in as an argument, what gets returned, or even what gets thrown, but a spy is just a passive observer, like your nosy neighbor. A stub, on the other hand, explicitly changes behavior. It knows when a function gets called, but it does more than just sit by and tell you that that function got called. Instead, it'll perform whatever action you want for that specific function. These differences further highlight when you would want to use a stub. If you're trying to control the flow of your application, or if you want to prevent undesirable behavior, like updating a database table or making a call to a payment gateway, then you should use a stub. If you only care that a function was called, then a spy is more of what you need. What if you need to set up a specific behavior and then verify that behavior occurred? Coming up, you'll learn about how Sinon handles that exact case.
-
Verifying Behavior with Mocks
Verifying Behavior with a Mock
Now that you've learned about spies and stubs, you've covered two- thirds of the test doubles provided by Sinon.JS. Sometimes you have a case where you want a test double that's partly a spy and partly a stub. In those cases, as you'll learn in this module, a mock is likely the right test double for you.
-
What Is a Mock?
As with spies and stubs, before learning how to use a mock, take a minute to familiarize yourself with what a mock is. According to Sinon's website, mocks are fake methods (like spies) with pre-programmed behavior (like stubs) as well as pre-programmed expectations. A mock will fail your test if it's not used as expected. There are a few things to unpack from this definition. First, a mock is a fake method. That is, like a spy, a mock knows what parameters it received, how many times it was called, etc. And like stubs, a mock has pre-programmed behavior. That is, a mock can return or resolve a value in the same way that a stub can. With those two properties, it's easy to see how a mock can be viewed as both a spy and a stub; however, the last line in the definition is probably the most interesting. If your application does not use the mock in the way that you specified, it will fail the test. This is new behavior. Remember back to stubs where you would set up various withArgs behaviors, and if the application called your stub with a value you didn't anticipate, it would return undefined. But a mock will actually fail the test. Another difference between mocks and the other two test doubles is how you create them. With both spies and stubs, you could create an anonymous test double, but with mocks you specify the object that you want to mock. For example, to mock out Wishlist, like in previous modules, you would call const mock = sinon.mock, and then give it the Wishlist module. Not only is the way that you create a mock different, but what happens after you create a mock is also different. On the left-hand side is what Wishlist looks like after stubbing it out. On the right-hand side is what Wishlist looks like after mocking it. You can see the difference right away. On the left-hand side, stub replaces the functions with a sinon.stub, but mock leaves them alone. At the bottom of the left screen, you can see what your stub looks like. At the bottom right of your screen, you can see what your mocks look like. Mock has a few extra functions including expects and restore. You'll learn more about these as the course goes on. It also has an object property. This is the object that you mocked out. In this case, it's Wishlist. All of this goes to show that mocks have some overlap with spies and stubs, but behind the scenes they're actually pretty different. Just as spies and stubs are not a one-size-fits-all solution, neither are mocks. The main use case for a mock is when you would typically use a stub, but you don't want to just state the behavior, you also want to set expectations on it. With this basic, albeit quick description of mocks, you're ready to tackle how to use mocks in the next clip.
-
Using Mocks
You've seen some of the differences between spies, stubs, and mocks just in how they get set up. Now it's time to see some differences in how they're used. By this point in the course, the code on the screen probably looks pretty familiar to you. You start by creating your spy or stub, then you perform an action, and then verify that your expected behavior occurred on your spy, but mocks are a little bit different. When you create a mock, the next step is to specify the expectations that you want to validate. That is, how do you want the mocked function to behave? The first group of expectations is similar to test cases on a spy. These deal with the number of times that a function was called. For example, you could set up your mock with the expectation that it'll be called at least once, and that's what the code on the screen does. It starts by calling the expects function. If you remember from earlier in the module, that was one of the functions that exists on a mock. In this case, it's telling Sinon which method on Wishlist it's setting expectations on. Then it tells Sinon what exactly it is that it's expecting. In this case, it's expecting the addItem function to be called atLeast one time. The new line on the screen at the bottom now shows what it would look like for this test to pass. Simply calling Wishlist.addItem and passing in an item to add. This looks very similar to what you learned with spies in terms of determining how many times a function was called. In fact, there's several expectations that would not surprise you. In addition to atLeast, there is atMost, and this allows the function to have never been called, or called up to and including the number you specify. There's never, which verifies that the function was never called. This could be useful in a situation in which you want to verify given certain conditions the save function to the database was never called. There's once, which verifies that a function was only called one time. As with spies, there's also a twice and thrice to verify that the function was called two or three times respectively. And of course if you need more than three function calls, you can use exactly to verify that a function was called the exact number of times that you specified. In addition to setting expectations on the call count, you can also set expectations on the arguments that have been passed in. The code on the screen shows an example of the withArgs function. It is very similar to the withArgs function that you saw when you learned about stubs. As you can see with the third function call on the screen, you can also specify exact arguments that you want to verify. This test would fail if the query included any other parameters. It will only pass with an exact match. Expectations always return an expectation object, which means you're able to chain them together. For example, the code on the screen chains the once and withArgs function to verify that findById will only be called one time, and that with the id of 1. At this point, you'd be forgiven for not being blown away by mocks. After all, apart from a slightly different way to create them, the other behavior seems similar to what you've already learned in the course. You can expect them to be called a certain number of times, just like spies. You can expect them to be called a certain number of arguments, just like spies. To see where mocks start to separate themselves from spies and stubs, continue on to the next clip where you'll learn about how to check that they behaved how you wanted.
-
Checking Mocks
So far, the only difference between mocks and the other test doubles seems to be that you set up expectations outside of the assertion phase, but if that's the case, how do you verify that the mock is behaving how you want? The answer is straightforward, but it might not be obvious. In order to check a mock, you need to verify it. The test on the screen might look somewhat familiar. Line 1 is creating your new mock. Line 2 is setting up the expectations on your mock. Line 3 is calling the system under test, in this case, the controller.getById function, and you want to ensure that when the controller function is called with an id of 1, it only calls the database 1 time with that id of 1. There's not yet an assertion or expect statement. Instead, with mocks you call verify. This will check all of the expectations you've set up. If any expectations have not been met, it'll return an error. For example, if you expected findById to be called with 1, but you called the controller with a 2, it would fail with the error at the bottom of the screen. There's actually two parts of this message. The first part tells you that it received a call that it wasn't expecting. In this case, it wasn't expecting findById 2, but that call occurred. The second part is the second line. It did expect findById 1, but that was never called. This gives you a bit of insight into how mocks work. They're very picky. Since you never told it that it might receive a call findById 2, it threw an exception. If you change the code so that it called findById 1 and findById 2, it still won't be happy. It'll return the error on the screen. The first line is the same; however, the second line provides more information. This time it did see the function call that you told it to expect, but like an uninvited guest, it didn't expect findById 2, so it still threw an exception. You have a couple of ways to solve this. First, you could make your expectation a little bit more of a generic. By changing once.withArgs 1 to atLeast(1), you're no longer specifying that it needs to be called with the ID, and you're no longer telling it that it should only be called one time. This does make the test pass, but it also weakens the test quite a bit. Your controller getById function could now call the database 1 time or 1000 times, and this test will still pass. Additionally, it could call the database with the wrong ID and still pass. So it's not a very useful test. Another way that you could write this test to handle the function being called multiple times would be to create multiple expectations. The code on the screen represents a test that will now pass. Both expectations on the single mock will be verified. This quick illustration shows that mocks can be quite brittle. As was stated at the outset, if a mock is not used exactly as expected, it will fail your test. As a result, be careful when choosing test doubles. Mocks do have their place, but most of the time you should probably prefer a spy or stub in order to accomplish your test.
-
Demo: Verify Payment
Enough with the theory. It's time to jump back into Globomantics' code base and see if there are areas that can be improved by adding mocks. If you check out the verify-mocks branch in Git for this module and open the book.controller.js file, near the bottom of that file around line 50 you'll see a function named purchase. This is the endpoint that Globomantics allows their API consumers to use to purchase a book for a user. It does this by creating a new transaction in the database which you can see on line 52. If you run your tests, you'll notice that there's no failing tests. You cleaned up all the failing tests in the last module, but since you know you're not connected to the database, and the test isn't failing, that can only mean one thing, there is no test for the purchase function. If you open up the book.controller.spec file, you'll verify that there is no test in that suite for the purchase function, and this is because how Globomantics does testing. Since the rest of their tests were connected to the database, they knew that if they ran a purchase test, it would add a record to the database. And if it added a record to the database, it would charge the customer. That's not something they wanted to try and do a few hundred times a day. Before adding a new test, go to the top of the file and require the transaction model right below the book model. Now at the end of the file, add a new describe block, then inside that describe block, add an it block that says that it should add a transaction. Next, you'll create the request. The parameters object should have an id of 1, and the body object should have both the user_id and amount properties. Now, create a response object. So far, this is pretty similar to how you would've created other tests, but now it's time to fake something out, the database call to the transaction table. Because it's a payment, it's best to verify that it was only called once and that it was called with the correct parameters. After all, users don't like getting billed twice for the same thing, and they really don't like getting billed for someone else's purchase. All of this makes a good candidate for a mock. So on line 97, create a new mock. The next step is to decide what expectations make sense on this mock. The first expectation is that it should only be called once. Then make sure that the one time it is called, it's called with the right arguments. Now, call the system under test, and make sure that you verify your mock after it successfully returns from the controller. Save your test and let your test suite run. Now you're greeted by this error, TypeError: Cannot read property then of undefined. This is because the create function on the transaction model returns a promise, but you've not yet told your mock about that. So Sinon is treating your mock as a normal function, and normal functions don't have a then function that can be called afterwards. This is where you can leverage a mock being a blend of a spy and a stub. So go back to your expectation on line 99, and at the end add a .resolves and pass it an empty object. Now when you save you test and rerun it, all your tests have passed. And just like that, you've ensured that the Globomantics API will only charge the right user and only charge them one time.
-
Mocks vs. Spies vs. Stubs
You have now seen all three of the available test doubles that Sinon offers. Before continuing on to learn more about the ways that you can use Sinon, take a brief moment to recap the differences. Earlier in the course, you learned that test spies are a lot like real spies. They snoop and monitor how your function is used. They don't alter how your function is used; instead, they simply report how it was called, and when, and with what arguments. You also learned that test stubs are great at modifying behavior. They present a false front. They intercept calls to real functions and instead perform whatever behavior you have them configured to do. Mocks are a hybrid of spies and stubs, but they're more than that. Mocks are a great way to ensure that the function you're faking out is behaving exactly how you want it to behave. While ensuring that a function is called exactly as you specify it is powerful, it also comes at a cost. Using mocks can make your tests more brittle because if a mocked function is not called exactly how it's configured, it will throw an exception; however, as you saw earlier in this module, mocks do tend to provide you with the right information when they fail. The example on the screen showed that it didn't expect to be called with an id of 2, and also it showed what it did expect was never called. Both pieces of information are helpful when trying to understand why a particular test failed. In reality, I find that I use mocks quite sparingly. I find that spies and stubs cover the majority of the test cases that I need. In fact for me, stubs are far and away the most common test double that I use; however, the payment method approach with Globomantics is a good case of when mocks could and should be used. It verifies two aspects of the exact behavior of the call. It ensures it was only called one time, and that it was called only for the right person. There are no more types of test doubles to learn with Sinon. Instead, the rest of the course will focus on ways to use the test doubles you've already learned. Up next, you'll see how you can reduce testing brittleness by leveraging Sinon's matchers.
-
Controlling How Parameters Are Matched
Controlling How Parameters Are Matched
You've now learned the three types of test doubles in Sinon.JS, spies, stubs, and mocks, and with those three doubles, you'll be able to fake out any object in the Globomantics code base, but Sinon is more than just three test doubles. It also provides you with some helpful utilities. The first one to look at will assist you as you tell Sinon exactly how to check the result object of a spy, or when to execute a specific stub. In Sinon lingo, these are called matchers, and you'll learn more about them in this module.
-
Verifying Creation Date
So far you've been focusing on Globomantics' book endpoint, but you've recently learned there's a problem with users. The bigwigs at the top wanted to roll out a customer loyalty program, but they found out that when a new user signs up, they don't track what day they actually signed up on, so they've asked you to look into it. Start by checking out the controlling-match branch from Git, then open the user.controller file. The first function in this file is the create function. Walking through this function, it creates a new Wishlist for the user, and then once that's done, it'll actually create the user with the parameters specified from the request body. Much like the Wishlist id is appended to the request body, you want to append a customer since field. Before you do that though, you've decided to write a test. So open up the file user.controller.spec.js. Currently, there are no tests in this file, which isn't too much of a surprise given Globomantics' approach to testing. Start by adding a describe block at the top for when creating a user, then add an it block for testing that it should have a customerSince field. As in the past, the next step is to create the request object, then add the response object. Next, you'll need to stub out the creation of the Wishlist object. Before you can do that, you need to add the wishlist to the list of requires at the top. Now stub out that wishlist create function, then tell the stub to resolve with the id of 1. Now, spy on your user.create function. And finally, you need to call the function on the controller that's going to create your new user. Once the controller returns, you want to check that the userCreate spy was called with the right arguments. This is where you get stuck though. How can you actually check the customerSince value? You could put a date, but if you use date.now it'll always be wrong. and if you don't specify the date, you're not really testing the new code that you're about to write that adds that customerSince field. This is where matchers come in. In the remainder of the module, you'll learn how you can tackle this exact problem, as well as other testing issues with Sinon's matchers.
-
What Are Matchers?
As you start to get into matchers and how they work, it'll be helpful to have a baseline understanding of what they are. Matchers are objects that contain a rule and allow you to control the specificity of your test. That doesn't exactly nail down what a matcher is, nor how it works, but it does state that matchers all contain a rule. This rule tells Sinon.JS how you should look at data. For example, a matcher can be passed to the withArgs function, and it'll inform Sinon to perform the match test on the arguments that were passed in. In fact, matchers can be used with a variety of functions. Specifically, calledOn, calledWith, returned, and withArgs. These functions should be fairly familiar to you by now as you've seen them with spies, as well as stubs, and some of them even on mocks. They all can take a matcher as their object. For example, the code you see on the screen is pretty similar to the code you've seen elsewhere in this course. You would pass in your expected object to the withArgs function, and tell the stub when it sees that exact object it should return true. Instead of passing in that object, you could pass in a matcher and tell Sinon any time anything matches the matcher that's passed in, it should return true. With that brief introduction to what a matcher is, the next clip we'll walk through the various types of matchers.
-
What Can Be Matched?
When using a matcher, it's important to note what kind of things can be matched. In reality, just about everything in JavaScript can be matched, but you need to be careful in how you do it. The first group of items that can be matched are specific values. There's four ways to match on values, and the first one can match on a number. You could use the code like you see on the screen. When a stub is called withArgs that matches the number 1517, the stub will return true. At first glance, that might seem like a weird matcher. Why would you do this instead of something like the code that's on the screen now, which is simply stub.withArgs(1517).returns(true)? In fact, isn't that matcher a little bit of an overkill, and possibly even less clear? The reason is that when you pass a number to match, it will do an equals equals comparison. This means that the two calls to stub on the screen will both meet the conditions of the matcher; however, only the first call would meet the stub that does not use a matcher because withArgs would by default use a triple equals. The next type of value matcher is a string matcher. Again, you pass your string to the match function, and this will do a contains check. This means that if you were to call your stub with Pluralsight, it would pass the test. Your stub would return true. You can also use a regex to match. For example, the regex on the screen will match values like A1, B1, C1, or A9, B9, or C9. Anything else will not match, so the stub will not return. This means that the first call would return true; however, the second call would return undefined since there's no matchers for that parameter. The final value type matcher is an object matcher. This time, the value you're passing in is an object. And with this particular matcher, it's checking that the object that is passed to the stub has at least the specified properties. That is, both of the calls that you see on the screen will match and have the stub return true because the stub is only matching that the object has an id value of 1. It does not care about any of the properties besides that one. In addition to the match function that you just saw, there are some properties on sinon.match that will also match values. Specifically, these are defined, which will match as long as the value is defined, or as long as the value is not undefined. Truthy will match any truthy value supplied. And of course, falsy will match any falsy value supplied. As was mentioned, these are properties, not functions, so they would be used simply as sinon.match.falsy when sending it to one of the functions that accepts matchers. In addition to those values, there are several property-type matchers that will match on the data type. There's a bool, or a number, or a string, or an object, or a func, or an array. Each of these will not pay any attention to the actual value. They will either return true if the data type matches, or false if it doesn't. So for example, if you did a sinon.match.bool, Sinon doesn't care whether the value of the bool is true or false. It simply cares that you passed it a bool and not a string. In addition to these six, there's also a map, set, regexp, date, or symbol that you can match on. Each of these matchers can be combined together to create other matchers. For example, the code on the screen shows how you could return a specific value if the user passes either a string or a number, and that's by using sinon.match.number.or, and then that's a function that takes another matcher. So then in that case you could pass in sinon.match.string. You can chain these together to your hearts content. Now that you've learned about matchers, it's time to jump back into the code to apply what you've learned to try and complete the test that you started earlier in this module.
-
Completing Date Verification
Go back now into your IDE and load the spec file for the user controller. Earlier in this module, you started writing a test that verifies that customerSince property is set to today's date when a user is created. This file has that test. You got down to the expect and didn't really know how to test that the property was a date, but now you've learned about matchers. So you can update your expect statement to reflect that customerSince should be a date. And since you're doing TDD, you don't yet have the code that will make that test pass. So go to the user.controller file. The top function is the create function. After line 12, add a new line. Add a new customerSince value on the req.body object and set it to a new Date. Now, run your test suite and see that all of your test passes. So you're now verifying that when you create a new user, the customerSince is of a type date. Go back again to your test. Currently you have an entire object that you're checking. That is, you're verifying that the email is test@test.com, and you're also checking that customerSince property is a date. This means that if the email was different, the test would fail, even though the customerSince property is still a date. And this can cause some confusion because the name of the test is that it should have a customerSince field. So the test should not fail simply because the email had changed. To mitigate this, you can combine the object matcher with the date matcher. So go to line 27, and in between the parentheses and the curly brace of the calledWith, add a sinon.match parenthesis. Then delete firstName, lastName, email, and wishlistId. And make sure that your closing parenthesis is added. Now when you run your test, it still passes. This is an example of how the object matcher works. You are currently telling Sinon that userCreate should be called with an object, and the only property that this test cares about is the customerSince, and you care that the customerSince is a date type. So just like this, you've now continued to improve Globomantics' code base because they're now able to verify that going forward, all users will have that customerSince, and it'll be a date.
-
Array Matchers
Now that you've seen and used some basic matchers, it's time to take a look at a bit more of the functionality that Sinon offers with the matchers. In a previous clip, you saw that you can verify that a parameter is an array by performing sinon.match.array. For example, the code on the screen returns true when the stub is called because it's passed an array; however, sometimes you want to know more than just if the argument is an array. You might want to know if it's actually the right array. For example, in your code you could need to set up a stub to handle one case where the user passes in a numeric array, which you've defined as an array that starts with the number 1, and then a separate case where the user passes in an alpha array, which you've defined as an array that starts with the letter A. The code on the screen can handle that case. It tells Sinon to match only in the case in which the array starts with 1 and return numeric, or A and return Alpha. In addition to the startsWith, there's also an endsWith check, which checks that an array ends with the data specified. So the other end of the array, one on the left-hand side so to speak, and one on the right-hand side. There's also a contains, which will verify that an array specified is contained somewhere in the array that's passed in. And finally, there's deepEquals, which allows you to specify exactly what the array should be in order to trigger the test double. Sometimes the previous functions aren't enough for you. Going back to the startsWith example, perhaps your business rules have changed. A numeric array is only a numeric array if every value is a number. You could test that with the code that you see on the screen. This will iterate over an entire array and check that every value has the matcher supplied. In this case, it's checking that it's a number. Conversely, your rules for an alpha array have also changed. If there's a single letter in the array anywhere, it's now considered an alpha array, and the code that you see on the screen now would accomplish that. In this case, it's saying that as long as some value, and the array is a string, then the entire array is an alpha array. But what if you're working with objects and not primitive values? If you tried to write a stub like the one you see on the screen, the value of the function at the bottom will not be true, but instead, it'll be undefined. That's because of how Sinon is doing the comparison of the array elements. To see how to solve that problem, check out the next clip on custom matchers.
-
Custom Matchers
Sinon has quite a few matchers available to you. Most of the time what you need will be accomplished with a predefined matcher, but sometimes you need more than the standard matchers. Sinon allows you to define your own matchers as well. How could you write a custom matcher that'll allow you to check an array and see if it contains an array of objects? Ultimately what you want is what you see on the screen here. You want to see if your array contains the object that you pass in. This would allow the stub call on the screen to return true because one of the items in the array, in this case the second item, is in fact an object that has an id of 1. The key to creating arrayContainsObjectMatch function is to create a function that accepts an expectation and then returns a match. To understand what's going on with this function, start at the end. The bolded portion of the code is simply using a normal match from the beginning of the module, and it's passing in your expectation. By using the sinon.match function and having that accept an expectation, the expectation could be a number, string, or even an object. After that, the matcher is used in the sinon.some function to determine if any item in the array matches your given expectation. Piecing all of that together allows you to extend the ability of the array matcher to determine if the array contains the object that you expect it to. You could also do something similar to have it verify every object in the array matches the object matcher. For example, the code on the screen will verify that your list only contains deleted users. This could be useful to know when stubbing out a function, as it lets you handle that differently than if there's users in your list that shouldn't be deleted. You're able to create new custom matchers by combining existing standard matchers, but there's another way to create a custom matcher as well. Earlier you learned that you could match on a specific value by calling sinon.match 1517 to match on any value that was a 1517. What wasn't covered at the time is that you can also pass in a function to the match object. The signature of this function is pretty straightforward. It simply contains a value that you want to test. The body of that function determines if the value that's passed in matches the expectation. That function is then passed to sinon.match, and you can also pass in an optional message as the second parameter to sinon.match, and that message will be used if the match fails. Here is an example of creating a new custom matcher. It takes the value and it checks that the value.name equals Charles Spurgeon, and then it checks that using the sinon.match(isCharlesSpurgeon). Clearly the code's pretty specific. It's only going to allow you to match users named Charles Spurgeon. And in fact, you don't even need a custom matcher to do that because you could just use the object matcher; however, as you've seen, you can create a curried function that takes an expectation and returns a function that accepts a value and checks that value. For example, the code on the screen would allow you to check if something is evenly divisible by passing in 2 to the isDivisible function, and then passing that to another Sinon matcher. At this point, you've moved beyond the basic test doubles that Sinon provides, and you've started moving into some of the helpful utilities. With matchers, and particularly custom matchers, you are the one that's in control of your tests and how they determine if something is working properly. Matchers are all about how you can verify your test doubles and ensure they're called correctly. Coming up, you'll learn about two utilities that will help you fake out behavior in your code as you learn about faking out XHR requests and timers in the next module.
-
Faking out XHR and Timer Calls
Faking out XHR and Timer Calls
At this point, you've learned about spies, stubs, and mocks. Additionally, you've learned about various ways you can match arguments that are passed into or returned from Sinon's test doubles. In doing all of that, you've been able to make Globomantics' code base better, but there are two problems in testing that tend to be difficult and painful to address. The first is testing front-end calls that are made to a back end. The second problem is interacting with time. If your code relies on new date for example, how can you write a test to verify that the correct date is used? You'll learn about both of these utilities in this module as you continue to help Globomantics' software get better.
-
Making External API Calls
Up until now, you've been focused on Globomantics' back-end code, but you've been doing such a good job with their tests that the front-end team wanted to see if you could help them out as they try to track down a bug that they have. Before jumping into the code, make sure that you're using the faking-xhr branch of the project. Also make sure that you run npm install as the front-end code has some new dependencies. Once that's complete, fire up the application by running npm start. Then point your browser to localhost:3000/currency. Here you'll see a page that Globomantics is working on which will let their users see the price of an item in their currency. The first field is an input. Since the page hasn't been released yet, you can still change this value. Once it's live, it'll be filled in with the pricing information of the book that the user is looking at buying. Then there's a drop-down that allows the users to see the price in Australian dollars, Euros, or US dollars. Finally, when the user clicks the button, it fetches some currency data and performs a conversion. The bug is that it doesn't seem to be doing the conversion correctly. If you select US dollars and click the button, you see that it tells you the price of a $10 book in US dollars is $11 in US dollars. The front-end team has an idea of what the bug is and they want to write a test to ensure that it'll never happen again, but there's a problem. Globomantics has realized that exchange rates are outside of their core competency. They want to focus on selling books and other media and not worry about staying on top of exchange rates. As a result, they don't have a service call for retrieving currency rates. Instead, they've decided to use a third-party API. In this case, they're using exchangeratesapi.io. While this is a free API, they've negotiated with Globomantics to pay for their usage because it's assumed that Globomantics will be making thousands of requests a day to the API, and that's where the problem with testing comes in. Every time the front-end test runs, it would hit the API and increase the count for the day that Globomantics would have to pay for. The front-end team has heard that you've got a good grasp on stubs and spies, so they thought you might be able to help them out. As you talk with the team lead, she shows you their currency.js file, and, as you assumed, it's making a $.ajax request using jQuery. You have an idea of how you could stub it out, or at least the happy path, but then you start thinking about how if they're going to test this, they should really test it, including checking for cases where the API returns a 500 error. So you quickly realize that a normal test double will not work. You tell the front-end team that you have some ideas, but you want to think about it for a bit and that you'll come back with something for them soon.
-
Running Tests in the Browser
Before solving the testing problem on the front-end team, it would be a good idea to become familiar with how to run tests in the browser. Up to this point, all the tests you've written have been run on the server side with Node.js, but to see the power of faking out XHRs it'll help to run them in the browser. Assuming you still have the app running when you ran npm start in the previous clip, jump over to the browser and browse to localhost:3000/mocha. What you see on the screen is a Mocha test runner in the browser. There's a couple quick things to point out. First in the upper right, you can see that there are 0 passing tests and 1 failing test, and you can also see that it ran 100% of your test suite. On the left-hand side, you can see a label, Currency Tests, and a smaller label underneath that called sample test. The sample test has failed with the error message that it expected true to be false. If you open up your dev tools and load the currency.spec.js file in the Sources pane, you can put a breakpoint on line 7, and then refresh the page. You can see now that your breakpoint's been hit. This is to illustrate that the test running in the browser has all the browser goodness at its disposal. That is, it's just like running any other piece of code in the browser. If you jump over to the IDE, you'll notice that there's a new folder compared to when you were just working with the back end. The new folder, public, has some interesting files in it. There's two HTML files. The first, item, is the web page you saw earlier. That allows users to see what the cost of an item will be in their currency. The second file, mocha.html, is the page that you just loaded. This is the Mocha test runner that will load the tests and run them in the browser. Next, you'll see a folder underneath public named javascript. Expanding this folder shows you three files. There's currency and its spec file, and there's a file price.js. Currency is the file that makes the AJAX call out to the exchange rate API to get today's rates. The price.js file is all the JavaScript surrounding updating the DOM on the item page. Feel free to take a look, but the course will only be covering currency.js and its spec file. To see a bit more detail about these tests work, open up currency.spec.js. It currently only has the sample test, but you're going to create a new test to test out the currency file, so feel free to delete this it block. Then add a new it block that focuses on the fact that when you convert from US dollars to US dollars, it should return the same price. And then in your function, pass the parameter done. Now this is the first time that you've passed in done to the function, but it will be necessary in those tests, and you'll see why in just a minute. Start by calling getPrices and pass in 10, the string USD, and then the variable CB. CB is the callback that getPrices will call once it's done fetching the data. Since you haven't created that yet, create it on a new line above getPrices. The callback is going to receive the price, and then it's going to perform a try catch block itself. And then the catch will catch any error that's thrown and call done with the error. Now let's take just a moment to walk through this. So the callback is going to be called by getPrices once the data's returned, and that's where the expectation is. It's expecting that the price of 10 to be converted to the string 10.00. If that's successful, in other words, if it doesn't throw an assertion error, then the done with no parameter will be called. This lets Mocha know that the test suite, although it's asynchronous, is now done and it can move on to the next test item. If the expect does throw an assertion error, then that'll be caught with the catch and it will be passed to the done, which lets Mocha know that again that the test suite, or in this case this test item, is done, and it'll return the error that it received. If you refresh your browser, you'll see that the test fails, and in this case, it's telling you that it expected 11 to equal 10. That's similar to what you saw at the beginning of the module when you used the actual UI to get the prices and it returned 11, but that's not the most interesting part. In your developer tools, if you open the Network tab and scan the list of requests, you'll see this one that's down at the bottom, latest?base=USD. This is the exchange rates API. Your test hit the currency file and the currency file used the browser's XHR functionality to make a call out to that API. This is the concern that the front-end team just shared. They want to test this function, because it's clearly wrong, but they don't want to hit that API all the time, and that is where faking out the XHR comes in.
-
Making a Fake Request
When you are working on stubs, you learned about the .resolves function that makes working with promises easier. Sinon simplifies working with XHR requests as well, and you'll see how in this clip. Inside Globomantics' currency class, there is a .ajax call. Underneath the covers of that jQuery function is an XMLHttpRequest, or an XHR. Even though it's got XML in the name, it's not limited to just XML data. Any data can be retrieved using this function. The code on the screen replaces the XMLHttpRequest object of the browser with Sinon's XHR. It also returns an object so that you can use that object for more information later on. In order to work with the fake requests, you need to be able to capture them, and that's what the code on the screen does. Sinon provides the onCreate function and passes in an XHR as the parameter. This allows you to capture them and control what happens for different requests. The code on the screen safely stores them in the requests array so that they can be accessed later on. There are several properties on the fake XHR object that you can use to help make informed decisions about how to respond to the client. For example, there's URL, and this is the request URL. It's the full URL including any query parameters that are passed in. There's the method, whether it's a get, post, put, delete, patch, or other HTTP method. This is the method that's used when making the request. There is an array of request headers. There is the body, provided that the method allows for a body. For example, get does not have a body. There's any username supplied, and there's also password, if it's supplied. Each of these parameters gives you information you can use when responding. For example, you could perform one action if it's a get, and another action if it's a post. In addition to details about the request, the fake XHR provides you with two functions that you can call. The first action you could take would be to simulate a network error by calling .error. This will call the onError handler, and with jQuery's ajax function, this will call the fail portion of the promise. The other action that you can call is the respond function. This function takes three parameters, the status, headers, and body, and returns a response with each of those. With jQuery's ajax function, this would hit the done portion of the promise. The code on the screen shows an example of calling respond. It's passing in a 200 okay, a Content-Type header, and the body of the request. You can accomplish the same thing by using four separate functions. Here you're setting the status to 200, then you're setting up the headers, and then the body, and finally calling respond. This option might be more attractive if you have a large body or several headers that you need to set, as that could make the one-line approach too long for your tastes.
-
Demo: Fake XHR
It's time to turn your attention back to the front end and see if you can help that team out. Start by running the test you created a couple clips back. When you execute it, check out the Network tab and verify that you see the URL latest?base=USD. This lets you know that you are hitting the real API, but remember, you don't want to be hitting that API. So open up your spec file again. In your it block, the first thing you need to do is tell Sinon to use the fake XHR. So add a line to do that. Then, add a line to create a new requests array. Then, you need to be able to capture the requests as they come in with the onCreate function. All of this should be done at the top of your test function before you've even created your callback. In fact, you can leave your callback completely alone. And you also don't need to change the line where you're calling getPrices, but after you call getPrices, you need to tell the fake request how to respond. Create a request by grabbing the first object in the requests array. Now, tell the request how to respond. The code on the screen responds with a 200, or Okay, then gives it a header for the Content-Type. It gives a value of application/json. And then finally, it takes a string for the body. In this case, the exchange rates API returns an object that has a rates object, and inside that rates object, there's a USD key and a value of 1. Now that you've typed all that in, save your test and go back to your browser. You can refresh the test, and you'll see that it's still failing. It's still saying that it expected 11 to equal 10, but if you check out the Network tab, you can see that there are no requests to the exchange rate API. Now that's the power of your fake XHR working, but unfortunately your test is still failing because it's returning 11 instead of 10, but at this point you can now check out what's actually happening in the currency module. Notice on line 11 that once the currency comes back, it's adding the conversion rate plus the price. Instead of adding, that should be a multiplication. That way, converting 10 USD to USD will result in 10 instead of 11. So change the plus sign to an asterisk to tell it to multiply, and then you can save this file and rerun your test. It now passes, and in fact it's even better because it passes without hitting the API. This means that even when the exchange rates change, your test will still work because you're only concerned about the logic of taking a price and multiplying that times the rate. You're not quite done though. So go back to your spec file, and notice that you're creating the XHR in your test. There's a problem. It's similar to stubs. You've now made the XHR object the fake one for the entire function. In fact, the entire test suite is now using the fake XHR object, so you need to restore it after the test. Underneath your describe block, create both a beforeEach and afterEach function. In the beforeEach, create your XHR and set up your create. And then in your afterEach, make sure that you call restore on the XHR. And make sure that you extract the variable creation outside of the beforeEach so that it's using the same variable. This will put the XHR back to what it should be for the browser. And just like with stubs, it's always a good idea to clean up after yourself.
-
Faking a Server
Handling a single fake XMLHttpRequest isn't too bad, as you just saw, but sometimes you need to handle more than one request. For those instances, Sinon provides an entire fake server. You might have a function, like the one you see on the screen, a function that performs several get requests and then uses that data to perform a put or post later. If you were to handle each of those as a separate request object, it could get quite messy and make your code hard to read. Every time you use a fake XHR, there are at least three things you need to do. First, you need to capture each request. Earlier you did this in the onCreate function, and you stored each request in the requests array. Later, you'll need to determine which request is which. Earlier you had it easy. You just had the one request, so you were able to use request 0, but if you have multiple requests, you'll have to dig into them in more detail to determine which is which. Finally, you have to tell each request how to respond, and then to do it. In your test, you did this with a single request after calling getPrices. It's not that any of these things are particularly difficult, but as requests add up, it's possible that the test gets messier. Sinon provides a fake server that will help with some of these tasks. As with the fake XHR, creating a new server is pretty straightforward, as you can see from the code on the screen. The code on the screen now shows one way that you can configure a server to respond to your requests. It takes two parameters. The first is a URL, and the second is a response. The response looks pretty similar to the values passed into the fake XHRs .respond function. The difference here is that this function takes an array instead of three separate parameters. This approach allows you a little more clarity in dealing with a request because you're tying the response to a specific URL. In addition to this response, there are several other ways that you can configure possible responses. The one you just saw will respond to all requests at the specified URL with the same body. If you want to set up responses based not just on the URL, but also on the HTTP verb, such as get or post, you can accomplish that by specifying the method as a string for the first parameter. If you don't want to specify an entire URL, you could use a URL regex. For example, perhaps you want all the requests to a specific domain to return one result. Even with regex's you can still limit it by HTTP verb, and much like stubs, you can provide a general response to the function, and any requests that don't match anything that you've set up will use this response. This allows you to set up an error condition for any URLs that you weren't expecting. After you've set up your responses and call your system under test, you now need to tell the server to respond. The third line on the screen will respond to all asynchronous requests. This is similar to how you operated with the fake XHR, telling it to respond after the function was called. When creating a new server, you do have the ability to provide some configuration parameters to it. One that I like is autoRespond. This tells the server to automatically respond to requests without the need to call .respond later. You can also tell it to wait for a specified amount of time in milliseconds, and then respond to that request. This would allow you to build in some latency and make it look more like a real server. While the server cleans up some things and makes it easier, it's still a fake underneath the covers, and it still overwrites the XMLHttpRequest object of your browser. So make sure when you're done with the server that you restore it. Whether you go with a fake XHR or a fake server, hopefully one thing is clear, using Sinon makes it simpler to fake out the request than it would have been without Sinon. Sure, there's still a decent amount of configuration needed, but it's less than you would need in order to fake out using stubs and spies. Additionally, the fake XHR and fake server give you control over what gets returned. If you want to simulate a server error, you're in control based on what status code you send back. Once again, Sinon is giving you power and control over your code base.
-
Faking a Date
Now that you've learned about fake XHRs, it's time to turn your focus on another faking tool that Sinon offers, fake timers. Back when you were learning about matchers, you wrote the test that's on the screen. If you remember, the goal was to verify that when a user was created, they had a customerSince field added to today's date. The test you wrote only ensured that it was the correct type, in this case a date. It was a good step, but I hope it left you feeling a little incomplete. After all, it could've returned any date and the test would have still passed. The problem with test dates is that the system under test will not always run for the exact amount of time. Different developers' computers will take longer to execute, or even the same machine, if it has different processes running from test suite to test suite could take a different amount of time to execute the same line of code. Creating a fake timer is very similar to how you create a fake XHR. As you can see on the screen, you simply call useFakeTimers. Similar to the fake XHR, not only does this overwrite the native timers, but it also returns an object to you so that you can perform various actions later. Telling Sinon to use fake timers will set the date back to the beginning of the UNIX Epoch, which is January 1, 1970. And so if you were to call new Date, you would get 1/1/1970. For some tests, that would be sufficient. You might not actually care about the actual time, only that you know what the date and time is; however, sometimes you do care about what the date and time is for your test, and Sinon has you covered there as well. The code on the screen will start the date on November 1, 2015. If you were to do a new Date after that, you would see that even though it's creating a new date without specifying parameters, it's still not creating a date for today. It's creating 11/1/2015. Because you're in control of the clock, you can advance it as you wish. You do this by calling the .tick function and specifying the time. If you specify a number, it'll advance the timer by that many milliseconds, or if you use a string, you can specify larger sections of time. You can do hour, minute, seconds, or, like the one on the screen, you could do just minutes, seconds. But no matter which one you do, you must specify two digits for each number. The code on the screen will update the date to be 1 minute and 20 seconds later than the previous fake time. As with stubs and fake XHRs, the fake timer has a restore function. This is perhaps more important than other restores since Sinon is replacing the system clock with the fake clock. Functions like set timeout and others that depend on the clock will not run on the fake timer unless you tell it to. So make sure that you're restoring your timer.
-
Demo: Cleaning up Date Verification
Now that you've learned about fake timers, you can put your knowledge to use by adding an additional test to your user spec file. Start by opening the user.controller.spec file. This file only has one test, and that's the test that you already wrote. For the time being, you're going to keep that test because while it does leave a gap for the possible value, it is at least verifying that a date is returned. Instead, add a new test. This time, you want to see that the customerSince field is set to new Date. Since the test is going to execute the same code as the previous test, you can start by copying the contents of that test. Now that you've done that, you've duplicated the test doubles as well. So go up to right underneath the describe block and add an afterEach function, and make sure that you restore your test doubles after each run. The next step is to create a fake timer. Since you know that you need to restore it when it's done, you can start by creating a variable to hold the timer as an empty object. Then add a restore to the afterEach block for the timer. I always restore by first checking to see if restore is a valid property on the object. If it is, then I call restore, but if it's not, then I don't get an error for calling a function that doesn't exist. For example, in this suite of tests, timer will only be used in my second test, and so timer.restore will not be a function after the first test, and I don't want it blowing up just because it's trying to call a function that doesn't exist. But now that we've got the restores out of the way, go back down to your new test. On the first line, set your timer variable to a fake timer. Now, go down to the then function of the controller call. Currently, it's checking that the customerSince field is a date type. Replace that with code that checks that it actually has the correct value. This is checking that the userCreate function is called with an object and one of the parameters of that object is the customerSince field. You want to verify that it's set to the value of new Date. In production, that would be the current timestamp, but since you've faked it out, it'll be 1/1/1970. This is one of those times where you don't really care about what the value is as long as you know what the value is. So save your tests and go down to the terminal and type npm test. You now have seven passing tests. At this point, if you wanted, you could delete your first test in this file. It's not providing any new or valuable information that your second test isn't also providing. That is, your second test is also implicitly verifying that the customerSince field is a date. And just like that, you've once again improved Globomantics' code base by setting up fake timers.
-
Coming Up
You've covered a lot of ground. You've learned about spies, stubs, and mocks. You've also learned about matchers, fake XHRs, and even a fake timer. There are no more test doubles, nor fakes that Sinon offers at this time. As a result, you've now learned the bulk of the functionality that Sinon offers; however, all of the work that you've done has involved carefully setting up and then restoring various doubles and fakes. In the next module, you'll learn how Sinon has even simplified that process for you.
-
Easier Set up and Clean up with Sandboxes
Easier Set Up
At this point in the course, you've been a huge help to Globomantics. You've introduced spies, stubs, and mocks into their code base. Thanks in no small part to your efforts, they're well on the path to having a robust test suite. Before you wrap up though, there's one more Sinon feature to learn about, and that is how to set up and tear down your test suite with less effort.
-
What Is Sandbox?
Sinon's solution for simplifying set up and tear down is to use a sandbox. A sandbox in Sinon is much like a sandbox in the real world. That is, it's a self-contained unit. It allows for experimentation. In essence, it's a playground where items outside of the sandbox are not impacted by anything that goes on inside the sandbox. This applies to Sinon as well. When it comes to testing, the idea of a sandbox becomes even more important. When you write tests, you want to ensure that they act as individual units. That is, the outcome of one test should not have any impact on the outcome of any other test. Further, one test should not be able to alter the state for another test. In that way, unit testing is already practicing a form of sandboxing. Having independent unit tests is an important principle in any language; however, it becomes even more important in a dynamic language like JavaScript. Since JavaScript allows you to replace individual functions in your code, the need to clean up after each test becomes even more important; otherwise, a function that you replaced in one test will remain replaced in another test, and that might lead to some unexpected behavior in later tests. As with all things in Sinon, before you can use a sandbox, you first have to create one. In order to create a new sandbox, you simply use the code on the screen, const sandbox = sinon.createSandbox. At this point, the sandbox variable will have the ability to create spies, stubs, and mocks, similar to how you saw earlier in the course. In fact, by default, a sandbox will only contain spies, stubs, and mocks, but as you've already learned, there's more to Sinon than just the test doubles. If you want your sandbox to use a fake server, or fake timers, you can pass in that configuration when you create your sandbox. The code you see on the screen shows you how you can tell the sandbox to use a fake timer. When you create a sandbox, you're allowed to pass in a configuration object. This object will merge with the default object. In this case, it'll turn on using fake timers. You also have the option to pass in a flag that tells the sandbox to use the fake XHR server you already learned about. Sinon has helpfully created a config object that you can use as well. It's called the defaultConfig object. I personally found that a little confusing because it behaves differently than if you didn't pass in a config at all. As you can see with the two code snippets on the screen, not supplying a config will tell Sinon to not use a fake timer, but calling it with sinon.defaultConfig will tell it to use a fake timer. So keep that in mind when you're creating your sandbox. If for some reason you didn't want to configure the sandbox to use a fake timer when you created it, that's okay. You can later decide to use a fake timer by calling the function sandbox.useFakeTimers. There's also a similar function for using the fake server. These functions allow you to create your sandbox, but delay making the decision of whether or not you need to use fakes until later. And that's as hard as it is to set up a sandbox.
-
Demo: Using Sandbox
Instead of adding new tests to the Globomantics code base, you can use your knowledge of sandboxes to clean up the existing tests. Start by ensuring that you have the sandboxes branch checked out. This will have all of the code that you've written up to this point. Once you have that, return to the book.controller.spec.js file. Throughout this course, you fixed and added several tests to this file. Most notably when learning about stubs, you added stubs to two separate tests. First when getting a specific book and the book does not exist, it should return a 404, and second, when getting a specific book and the book does exist, it should return a 200. In both of these tests, you stubbed out the findById function. After you did this, you found out you needed to restore those stubs, so you added an afterEach function that would clean them up. Using a sandbox here won't be much more involved than what you've already done. Start by adding a new line above the afterEach function, and create a variable named sandbox, and assign a new sandbox to that variable. Now that you've created your sandbox, you need to use it in the two tests that were just mentioned. First, go to line 60 and there you'll see this line, const find = sinon.stub(model, findById). Replace sinon here with the sandbox variable you just created. Next, go down until you find a similar function on line 80, const find = sinon.stub(model, findById). Once again, replace the sinon variable with the sandbox variable that you just created. At this point what you've done syntactically has been pretty minor. Instead of calling sinon.stub, you're calling sandbox.stub, but what you've accomplished behind the scenes is a little bit more than that. By calling sandbox.stub, you are still creating a stub, but you're also placing that stub into your newly-created sandbox. If you save your tests and then run npm test, they still all pass. This is because you're using a fully legit Sinon stub behind the scenes, and that stub is still faking out the findById call on the model, and so your afterEach function on line 12 is still restoring the call. You could replace this line of model.findById.restore and model.findById.restore with a single call to sandbox.restore. And if you save the test once more, you'll see that they're still passing, but with the sandbox restore, you don't have to check if restore is function before calling it because restore will always be a function on the sandbox. At this point though, you wouldn't be faulted for asking what exactly a sandbox got you. It's not yet doing anything that you haven't already done in the course, it's just using a slightly different variable name. So to see how sandboxes are even more valuable, switch over to the user.controller.spec.js file. If you remember, it was in this file where you tested that the customerSince field was set to the current date and time whenever a new user is created. To do this, you stubbed out the wishlist.create function and you also used a fake timer. Much like the book.controller.spec.js file, start by adding a new line after the describe block for When creating a user, and then create a sandbox; however, unlike last time, pass in an object to the function to tell it to use fake timers. Next, find every instance of sinon.spy and sinon.stub and replace them with sandbox.spy and sandbox.stub respectively. Next, find the test named Should set the customer since field to new Date. The first line underneath this test creates a fake timer and assigns it to the timer variable. You can delete this line since when you created your sandbox, you told the sandbox to use fake timers. Once you've got all of that done, you can go back up to the main describe block near the top of the file. You can now delete your timer variable from line 10. Finally, you can go into your afterEach function. As you can see, before you're restoring each of your test doubles and fake timers manually. You can replace these three lines with a simple line of sandbox.restore. And that shows some of the power of the sandbox. No matter what gets created in any of the tests, you can restore everything with one line. So now you can create a sandbox when you start your tests, and as long as you call sandbox.restore in your afterEach function, whatever stub, spy, mock, or fake will be cleaned up when you're done. This ensures that there is never a function that is faked out running loose in your code wreaking havoc.
-
Other Sandbox Functions
Now that you've updated the code base with some sandbox usage, take a few minutes to learn about some of the other functions that are available to you via sandboxes. In the demo, you used a sandbox to stub, spy, and fake out timers. You can also use a sandbox for mocks, but if you remember, you validate mocks slightly different than you do spies and stubs. You need to call .verify. You can still do that with a sandbox, but you would actually call it on the entire sandbox like the code you see on the screen, sandbox.verify. By calling sandbox.verify, you will verify that all of the mocks and the sandbox are verified. Additionally, you can verify your mocks and restore them all at the same time. This could be a tempting call to make as every mock needs to be verified and every mock needs to be restored. While it might be tempting to use this call, I would advise that you verify your mocks near where they're used. When you have a mock used on one function, but you don't force the verification until later in an afterEach, then it can become confusing about what is actually triggering the verification. Not only can sandboxes handle the test doubles you've seen so far, but they also have the ability to replace properties on objects. This is done using the code you see on the screen, sandbox.replace(Wishlist, count), and then a function that just returns 2. With this code, you're replacing the count function on the Wishlist module with a function that always returns true. JavaScript is dynamic to the point that you could easily do this yourself with a second block of code on the screen, and just say Wishlist.count equals a function that returns 2; however, there's a problem with the second line. When you're done forcing it to return 2, you have to have some way to tell it to go back to the original version. With a sandbox though, that's accomplished by simply calling sandbox.restore. You might be wondering why you would use this instead of a stub, which would also perform the same role. The code on the screen now is not replacing the count function, but is instead replacing a count property and forcing it to always returns 2. And, like the previous example, you could still call sandbox.restore and it would set the property back to its normal functionally. Additionally, if you have getters or setters in your JavaScript that you want to replace, you can do that with a sandbox as well. By this point, you're probably familiar enough with Sinon to guess their names, but they're simply replaceGetter and replaceSetter, and then you supply the information that those functions need. Whether you're trying to use test doubles, fake timers, a fake server, or replace individual properties, sandboxes can make the setup easier, and even easier than the setup is the cleanup. With a single call to sinon.restore, your sandbox will place your objects and functions back to their original pristine condition.
-
What’s Next?
What's Next?
At this point, you've learned the major features of Sinon.JS. You've also helped clean up Globomantics' code base. You are ready to go out into the real world and start using Sinon on all of your projects. Before heading out though, take a quick minute to review the various Sinon features that you've learned. After that, you'll learn about some of the next steps that you can take to get started with Sinon.JS.
-
Big Picture
Take a minute to review the major features of Sinon.JS. The course started by tackling the bottom rung of the test double ladder, the test spy. This double's focus is observing what happens with a function. It's like a spy in real life. It's doesn't interfere with any of the function calls, but it can tell you everything you need to know about them. In fact, when you learned about the spy, you learned a lot of the fundamentals of Sinon. For example, you learned how to create a test double, as well as how to check the various behaviors of a function. While the code on the screen is probably quite familiar, and even obvious now, you first saw it when you learned about spies. After learning about spies, you learned that they would not solve all the test problems in Globomantics' code base. For example, there were tests that needed to check that specific data was returned from the database. With stubs, you're able to prevent the default behavior of functions and instead dictate how you want that function to behave. While spies provide a lot of behavior, stubs allow you to be in control over the flow of the application. For example, you can stub out a function to force it to throw an exception, or you can stub it out and tell it to resolve a promise. Additionally, stubs give you the power to prevent undesired behavior. In the case of unit testing, that can mean you prevent test records from being saved to the database, or preventing a long-running API call by using a stub. This was something that Globomantics desperately needed in their code base. Once you got passed spies and stubs, you'd covered the majority of the use cases for faking out calls; however, there was still the occasional use case where you not only needed to use the functionality from spies, but also from stubs. With Sinon, you can accomplish this using mocks. With Globomantics' code base, you used to a mock to ensure that the correct user, and only the correct user, was getting billed a single time when they made a purchase. You made some code similar to what is now on the screen where you not only specified what arguments you expected, but also the exact number of times that you wanted the function to be called. After learning about spies, stubs, and mocks, you started to learn some of the additional features that Sinon offers. You started with matchers. Matchers have the ability to control how Sinon matches various arguments. For example, the code on the screen showed you how you can have a stub return various arguments based on how it matches the various parameters that are passed in. It was also with matchers that you first saw some extensibility of Sinon because you created your first custom matcher. Sinon wants to provide you tools and utilities to solve whatever test fake needs you might have. After learning about matchers, you learned about some of the more powerful features of Sinon. It provides you ways to fake out an XHR from your browser. This allows you to not only test your server-side code using Sinon, but to even use it when testing your client-side code. By using the fake XHR, you ensure that your tests do not need a running API in order to work. Perhaps one of my favorite features is the fake timer. This feature allows you to test code that depends on JavaScript's date object. By using Sinon's fake timer, you're in charge of how dates behave in your code. For example, using the fake timer, Globomantics was finally able to test that they were correctly setting the customerSince property when a user was created. Without this fake timer, they had a hard, if not impossible task of verifying the date behavior. Finally, you learned about the sandbox functionality that Sinon offers. It really is the culmination of everything you'd already learned. Sandboxes can contain spies, stubs, mocks, and fake requests, but sandboxes allow you a way to quickly and easily clean up after yourself. Based on everything you've learned in this course, you should have a high-level of confidence that Sinon will provide what you need when writing tests for your code base.
-
Additional Libraries
Everything you've learned so far has come straight from the Sinon library; however, that library might not cover all your needs. As a result, there are a handful of useful libraries that will work with Sinon that would be good to know about. The first library to look at is sinon-chai. Throughout this course, you have seen chai in use. The expect-style assertions that were used in the Globomantics code base came from chai. Early on in the course, you created a spy on model.create and you ended up testing that it was called by writing the code return expect(model.create.called).to.be.true. In fact, this code is probably pretty common for you at this point; however, if you're not a fan of that syntax, you can use sinon-chai. That same expect would now look like the code you see on the screen, return expect(model.create).to.have.been.called. I personally prefer the use of sinon-chai whenever I use Sinon because I like how my expect statements read with that library. Additionally, it works on more than just called. For example, you could use it with calledWith, or returned, or throw, or even the variations of those functions, such as calledWithExactly. The point is that sinon-chai can make your code easier to read by using a fluid syntax, if that's your preference. Speaking of libraries that help with assertions, there's another library called sinon-called-with-diff. When learning about spies, I mentioned that I will often use args to check my calls instead of called because called will simply return a Boolean, and this is where sinon-called-with-diff comes in to play. When this library is used, and you use the calledWith function on stubs, it'll actually update the test output when the test fails. That is, it'll tell you that you expected test, but got hello, as an example. One word of caution with this from looking at the source code on GitHub, this library only works with stubs. It does not work with spies. So while I like the idea, the implementation might leave something to be desired. Of course, since it is on GitHub, there's always a chance to submit a pull request and have it work with spies. The two libraries you just saw were an example of general utilities, or helpers. There are two more to be aware of. The first is sinon-fluent. If the code you're creating uses a fluent syntax, then this library will help you fake out functions in your fluid chain. Additionally, if you want to have typed spies, someone has created sinon-typed just for you. This will allow you to create a stub of whatever type you're using. While I've not used this library myself, it does seem to have potential if you're using TypeScript in your project. There's a few more libraries to make note of, but these are going to be a bit more specific. The first library comes in handy if you're using MongoDB and the Mongoose ODM. Often when using Mongoose, you'll find yourself constructing a query on a model that involves a lot of chained functions. For example, you might supply a query parameter, a sort parameter, and tell it to take a specific number of records. The sinon-mongoose library makes all of that much easier to mock out so that you can test the database calls in a repeatable fashion in your code base. The next library is sinon-express-mock. Throughout the course, you saw code that would create a fake request and response object for Node. That was done using a package node mocks HTTP; however, the sinon-express-mock also provides that functionality. It allows you to generate fake requests or response objects that you can pass in to your Node.js controller functions. I can tell you first hand that it is easier and faster to use a library like this than to try to write your own stubs and spies to perform that function. So if you're using Node.js, definitely check out this library. The next library focuses not on Node, but on the browser. If your application is using APIs specific to Chromium, you might not want to hit those APIs in your tests, and with sinon-chrome, you won't have to. It provides stubs that you can use in place of the actual API calls, and despite its name, sinon-chrome will also work with Firefox in addition to Chrome. Much like with express, you'll be better off if you use sinon-chrome instead of trying to roll your own fakes. The final library is similar to sinon-chrome in that it attempts to provide stubs for an external utility, but this is not a browser. Instead, it's the AWS SDK. This library uses Sinon to fake out the various calls that you might need to make when working with AWS. For example, if your code needs to retrieve an object from S3, this library will help you mock out that call. This was just a quick survey of a few of the libraries that either use or extend Sinon. If you want to find more, head over to npmjs.org and search for Sinon. As of right now, there are 283 packages, and the list is always growing.
-
Final Thoughts
The goal of this course was to get you up and running with Sinon.JS, and I hope you now feel comfortable using its various features and functions. Keep in mind, however, while the course covers a lot of Sinon, it doesn't cover every single function that you can call on test doubles, nor does it cover every piece of configuration. For example, with the fake server that you could use. You can always check out the docs over on Sinon.JS. They do cover every single function and configuration parameter, and often their examples will serve to refresh your memory on how something works. If all else fails, keep in mind that Sinon is open source. You can find the code on GitHub, or if you go to the sinonjs.org page, there's a link in the header to their GitHub repo. The code is fairly straightforward. For example, when I was trying to learn the difference between stubs and mocks, I was able to pull up the source code and read through the mock file and see that mocks extend stubs. In fact, later there were numerous times throughout the course when I would want to make sure that what I was saying was exactly right, and I would pull up the source code and read it so that I could make sure I understood what was going on behind the scenes. Finally, thank you. I truly appreciate the time you've devoted to watching this course. I hope you enjoyed it as much as I enjoyed creating it. I'm a firm believer in unit testing and Sinon has been invaluable to me in my JavaScript projects. I hope it will be for you as well. Feel free to reach out to me on Twitter. I'd love to hear from you.