What do you want to learn?
Skip to main content
by Jonathan Mills
Start CourseBookmarkAdd to Channel
Table of contents
Types of Testing
Now ultimately when it comes to writing tests, there's three different styles of testing, or three different types of tests that we're going to write. There's unit tests, integration tests, and functional tests. And I've used the puzzle piece icon just to give you an idea of what it is we're talking about, and really just this sums it up, and you can see totally how all this works. Unit tests are one puzzle piece, integration tests, we've got a couple of them put together, but one of them is still off by itself, and then functional tests, we're testing the whole thing end to end. And let's break this down a little bit, but this ultimately is the picture of the different levels of testing that we might do. Now in this course, we're going to do unit and integration tests. I'll show you how to do some unit tests, we'll add some integration tests kind of as we go, and I'm going to seamlessly kind of flow back and forth, depending on what we're testing and how I want it to work, but functional testing is kind of outside the scope of what we're talking about because that's not Mocha core anymore, there's wrapper pieces that are outside the scope of what this course is really all about. Alright, so let's talk unit testing here for just a second, just a single puzzle piece. Now the idea here is we're going to find the smallest available piece. And so either it's just a single function, or just a few lines of code, or one small little unit, and we're going to just test that, and that's it. And we'll make sure that that thing does exactly what it's supposed to do, regardless of everything else that's around it. We're just going to mock everything else. If it needs something else, we're just going to create a pretend one. And we'll talk about that here in a few modules with Sinon. We're going to make sure that just this thing works. And so an example here is I've got a function called isAuthorized that basically just checks to see if a role exists inside my needed roles. Basically it's hey, I've got an array of roles, does this thing exist? And so what I can do is I can create a test that's just going to pass in an admin, in this case, in my roles, or admin user, and I'm just going to say hey, does this give me back what I expect? I'm only testing this one little thing outside the scope of everything else, and this is overly simplistic obviously, for the sake of a slide, but you get the idea is if I have a function that does something potentially complicated, I'm going to create a test to make sure that function all by itself, separate from everything, is going to do exactly what I want it to do, and nothing more, nothing less, so that I can test edge cases and all that stuff. And that's essentially a unit test. And people will push back on a unit test and say well, but if it works in the overall thing, then it should work. And that's true for the most part, but when I'm building something like this function, isAuthorized, and I want to just make sure this thing works, it's much easier for me to write to a test and wrap this one little function and tweak the function until it works with a test than it is to build the whole big application around it and have to run everything every time I make a change, just to make sure it works. So that's kind of the beauty of the unit test, is I can make sure just this one little thing works. Now that's only part of it, because we have to tie some things together. Your function has to work in the larger scope, not just all by itself; it actually has to interact with other things. And so that's where integration tests come in, and when we're integrating our code in with other pieces of code. And we here can test interactions, and we'll talk about some of this when we get to Sinon and spies. And we could say hey, make sure this thing calls this other thing, and they all have to work together, and that way we can keep an eye on our code and make sure everything's working, but still see and make sure that our code is interacting with different pieces, and all the pieces kind of flow together, kind of like the puzzle over here. In this case, we're still going to mock those outside resources. I don't necessarily want to have to hit a database, or I don't want to have to hit an API, I want to be able to just test my little thing, or my application, without worrying about all of the external pieces. And so we'll still mock those things, and we'll talk about that when we get to Sinon and stubs, and things like that. And last we get to the whole puzzle, all put together, or our functional testing. And this is where we test everything all together, the entire application end to end. We start on the outside, we end on the outside. There's tools that will wrap everything to see how we can just make calls to our application and see what the response the application gives. And we don't necessarily have insight into what's going on. And we have a term for that, it's called black-box testing. And so actually let's just do this, and black-box testing means I have no idea what's going on inside my application, I just know I hand it this, I make an HTTP call to it, and it returns this thing. And so that's what we test, just so we can make sure everything fits together, and it's all nice in a tight package. And now that's a little bit outside the scope of this course, but I want to make sure we touch on it so that you know this exists as a thing too. And for the most part as a developer, you're going to focus on the first two pieces. We're going to unit test as we're building our code, and then we're going to integration test to make sure things kind of fit together. And we're going to leave the functional testing, a lot of times to a different group, or somewhere else. So we're going to leave that to another day or a different course.
Now we can't have a course on a Node topic without talking for a minute about Node versions. Node can be interesting to deal with when you're dealing with versioning, because they have a very aggressive release cycle. So this is their schedule through Node 8. And by the time you're watching this course, who knows what's going on, but as of right now Node 6 is an active LTS, but Node 7 is current. Node 8 will be coming, and this says in April, but it's been delayed a month or so to coincide with the V8 release. So that'll be helpful, but things change very, very rapidly. And if you're watching a course that's using Node 7, but you're currently using Node 4, it can cause problems, and so there's an easy way that we can deal with this by using something called NVM. And NVM is awesome because it's going to help us use different versions of Node depending on what we're looking for. So you can run out to GitHub and see github.com/creationix/nvm, and install Node Version Manager. You can just click on this install script, it'll tell you how to install on Windows or OS X or whatever you're at. And once you have that, see I can come over here and I can say, which Node? And see I'm running 6.10.2. And that's great, but I want to use, let's say Node 7, because the current version of Node right now is Node 7.9. So I can do nvm install 7.9, and hit Enter, and it's going to install for me the latest version of Node at this moment. So now it's installed and I can say which node, and see now I'm using 7.9. But if I come back and I do nvm use, and I can do --lts, and say hey, what's the current version of node under lts? Now I'm using 6.10.2. I type which node, and I'm getting 6.10.2. Okay, for this course since LTS is 6.10.2, that's what I'm going to be using. But, like I said just a minute ago, things change. So let's talk a little bit about what that's going to look like for us.
Alright well with that, what if something changes? What if one of these Node versions or a Mocha version changes and things break? Well here's what we're going to do with that. I have a GitHub repository, jonathanfmills/TestingNodeWithMocha. I've actually created a bit.ly link, JonsMochaCourse, that you can use to come out here to look. And if something changes down here at the bottom, we'll put what's changed and what you can do about it. Also in each module, so if you go to Module 5, here's all the code that we're going to use in Module 5, and if things change, I'll update this as well to make sure that everything's going to work. So check this out, bit.ly/JonsMochaCourse, to get the demo code, but also keep track of what's going on if things change.
Alright, so that's it for this introductory piece, and really it was just kind of a let's get everybody on the same page. We talked really quick about unit versus integration, versus functional testing, and kind of how that plays into what we're doing. I showed you NVM, so you make sure that you can switch between versions of Node so you can play with this course and still do work at your real job, or do whatever you do and be able to flip back and forth between the Node versions. Also I showed you the GitHub repo, bit.ly/JonsMochaCourse, so you can keep up to date with what's going on. Alright, now that that's all done, let's actually go get some work done. So let's go get some Mocha installed and start writing some tests.
Testing with Mocha
Alright, now the first thing we need to do is install Mocha. That gets us started, it gets us going somewhere. So open up a new directory, just a clean, empty directory, and the first thing we're going to do is the general Node setup. We're going to nmp init. And we're just going to kind of take the defaults, move our way through, just so that we get our package.json file. If you do right, now you've got a package.json sitting here ready to go. Now the next thing we need to do is get Mocha installed. And we're going to install Mocha twice. The first thing we're going to do is we're going to npm install, -g is for global, so this will give us access to it at the command line, and we're going to do mocha. Now when you hit Enter it's going to look different than when I hit Enter because I've already got it installed, but now you should be able to just type mocha, hit Enter, and it's going to give you an error. You should also say mocha --version, I'm using version 3.2 of Mocha, it's the latest version as I'm recording this course. Again if there's any breaking changes in versioning, check the GitHub repo for this course, and we'll update as we need to. Alright, so that's half of it. We've installed Mocha globally, so now we have Mocha at the command line, we also need to install it locally. So nmp install --save mocha. And there it is, our dependencies, mocha. Alright, so now we're installed, we're ready to go. Sweet! Let's write a test.
Our First Test
Alright, so over here in our directory, this the empty directory that you created to kind of get things started, we're going to create a new folder called test, and this is where we're going to drop all of our tests. And the first thing we're going to do, we'll create a new file and we'll just call it starting.spec.js. And we'll get into the naming convention a little bit later, for right now let's just get some stuff done. First thing we're going to do is var assert. We're going use assert as our assertion framework for right now. In the next module we'll add in something different, but for right now I just want to focus on one thing at a time. So require assert. Now, we have to describe our thing, remember we're using the BDD style. So we're going to do describe Basic Mocha Test, then we do a function. Now as we're describing our Basic Mocha Test, we have to give it a list of things that it does. And it should throw some errors if things don't work, and that takes a function too. Now the way it throws errors is through this assertion thing. So right here we're just going to do assert.equal, let's just say 2 and 3, just like that. Now this obviously is going to fail, and that's kind of the point. I just want to give you a test that's going to fail, and then we can talk about what failure means, and all that kind of stuff. So let's run this test. In the root of my directory, I'm just going to type mocha. And what Mocha's going to do is it's going to look for a test directory, it's going to look in that test directory, and it's going to execute things that are in it. So I'm going to hit mocha and I'm going to hit go, and it's going to run my test. And here's a couple of things to point out. So Basic Mocha Test, describe Basic Mocha Test, Basic Mocha Test. Should throw errors, should throw errors. Hey, and it's failing. But then it gives you a very helpful hey, Basic Mocha Test should throw some errors, and it actually is doing exactly what I'm describing, it's throwing an error. So now you've got a test. And if we come over here we say assert our equal 3 and 3, and save that, we run it again, boom! Hey, now I've got my test passing. Alright, that seems a little bit, I don't know, kind of black-boxy, so let's talk a little bit about what an assert is and how a test passes and fails so that we can understand what's going on here.
What Is an Assert
Alright, so let's start over for just a second so that we can talk about what this assert thing means, because if I've got a failing test, 2, 3, it has to let the system know in some way, that hey, this test fails, and at some point as a developer, you're going to be writing tests, and you're going to be dealing with the database, or you're going to be doing something, and you're going to try and do this. You're going to try and then you're going to catch, and then you're going to come over to your window, and your tests are going to pass, and that's weird because your tests shouldn't pass because you're asserting something that's false. So this test should fail. And this starts to make sense when you log this. So let's do a clg your error, and see what happens here. Let's run this test again; now look at this. Basically what's happening is the assert when it doesn't equal, when things aren't right, it just throws an error. And it throws an assertion error, and you can see all the stuff that goes along with this, the message and the operators and all of that is built into this error, and that's what is used to give you the output and to tell Mocha that the test is failing. You could just as easily inside here, just do a throw, and you can throw, let's just say message thrown error. And now think about it, is this test going to pass or fail? I'm not asserting anything. And look, it's failing. It should throw an error, and there's your error right there. So that's all an assert is. It tests something, and if it passes it doesn't do anything, and if it doesn't pass, it throws and exception and stops your test. So just be aware that that's kind of how that works, so as you get into try/catch blocks, or you're trying to do your own thing, you can just throw an exception and that's going to fail your test.
Testing Something Real
Alright, now that we've got some basics down, let's actually try to test something that's at least pseudo-real. Let's still pretend a little bit, but let's start getting the framework of some real actual tests going. So let's come back out here and create a new folder, and let's call it controllers. So let's create a controller that we're going to test. And we'll create a new file under controllers called, auth.controller. And in my auth.controller let's just create a very basic controller that does two things, or in this case let's just do one thing. So we have a function isAuthorized that you pass in a set of roles, and the role that it needs, and it just checks to see if that role exists. And we're going to start basic, we'll make this more complicated as we go. So there's our controller. Now under test let's create a new folder called controllers where we'll test all of our controllers. And we'll create a new file under controllers called auth.controller.spec.js. And in this one we're going to do two things right up at the top. We're going to do our assert, but we're also going to pull in our authController. So let's go get our authController, we'll pull that in, and then we're going to describe it. So here's how this works. You do a describe and then a function. And in this case we are describing our authController. But we're not just describing the authController, we're, in this case, describing the function isAuthorized. So we're going to also describe, this is where you can kind of get some coolness, we're going to nest these describes so we can get a clear understanding of everything that's going on in our application. So in this case we're describing isAuthorized. And we'll do a function call here. Now what should isAuthorized do? IsAuthorized, it Should return false if not authorized. And then we'll write our test in here. Now in this case our test is going to be pretty easy to write, because basically we're going to assert our equal false authController.isAuthorized user, admin. So admin is not part of the user roles, so therefore it should fail. Alright, let's try this out. Now I'm going to tell you right now, this is going to not work, because what Mocha does is it looks in the test directory and it pulls all the tests. Well we're not in the test directory. We are in the test directory, but we're not all in the test directory. We've got to tell it to go deeper. So we're going to do this, we're going to pass into mocha a directory structure. And we'll just hey, everything is in test and below. And you can even say, hey, just do the specs. Now when I run it, AuthController isAuthorized should return false if not authorized. Now this other one is still failing, and let's just fix that real quick so it doesn't fail. We'll just delete it. And actually now since no exception is thrown, it should just pass, even though it's not really doing anything. But that's how that works. So isAuthorized should return false if not authorized. There it is right there. Let's go back in here to my auth.controller.spec, and let's actually do the same thing again. It should return true if authorized. And we'll say, hey, it should return true. Now this test should fail because it's the test that's failing, because admin's not in there. So now if I save it, now I have the role. Let's test it. Boom, hey! The cool thing about the BDD style, and this is why I like the BDD style, as we're writing this we are describing the requirements for the function isAuthorized. So in the authController, isAuthorized should return false. If not authorized, should return true if authorized. Alright, now we have an actual test of something that could be a real controller in a real application. Now let's start to twist things and make things a little bit more complicated, so we can see how to adapt as we go.
Now whenever we run our tests, we've got to type this whole big long thing, mocha.test, blah, blah, blah, with all of the directory specifications and everything, and that's kind of painful. So let's do this. Let's copy that and come into our package.json. Now see right here we've got a scripts section and it has a test section already built in, it just kind of did it for us, which is kind of awesome. We're going to come in here and we're going to paste the mocha test. Now you can't have double quotes inside double quotes, so we'll delete that, delete that, and put single quotes around it so that it'll work, but now what we get is whenever we do npm test, it's going to run our Mocha test for us. And so let's try that out. Npm test, and now we've got our test running. So let's just go ahead and set up your package.json wherever so that your test just kind of runs things for you.
Working with Timeouts
Now sometimes functions take a while to return, or take a while to get something done, and so let's go back to our auth.controller, and instead of setTimeout of 0, let's actually set this to 2100. So that's 2100 ms, which the default timeout for a Mocha test is 2000 ms. So now these tests should fail. Error timeout of 2000 ms exceeded. So there is a built-in way to deal with this issue, and that's by modifying the context for Mocha. You can actually access the Mocha context directly by using the this keyword, because this function is executed inside Mocha, so I can actually do this., and I could do this.timeout, because this is executed as part of Mocha, this works, this.timeout, and change it to, let's just say 2500 so that I'm changing the timeout of this test to 2500 ms. So I'll save that, we'll run our test again, and now everything works. Alright, now this causes a problem when you throw in ES6, because actually I have in VS Code I actually have ES6 Mocha. So if I type describe, notice how it's automatically doing arrow functions. And I have not been doing arrow functions, despite the fact that we're in Node and we have access to all of these things, if this became an arrow function, arrow functions are very cool because it changes the way this is bound. This becomes bound to the lexical context instead of the call path. So if I do this one too, what's going to happen, now I'm using all arrow functions, check this out. I'm not changing my timeout anymore because of the this keyword. So what Mocha suggests is to not use arrow functions, which is why I have not been using them up until now, and I won't use them through the rest of the course. Now could these two describes up here be arrow functions, and those its? Absolutely, you could totally do it, it's just better if you know that in some cases all of a sudden you might have to use the timeout, or you might have to use something else, one of these this available things in Mocha, then now you've got to go back and undo your arrow functions. So for the sake of Mocha and everything we're doing here, we're not going to use arrow functions for this specific purpose.
Alright, now in fairness to all of this, this is a pretty simplistic test. I mean we don't usually do all of this; usually there's a little bit more complexity to it. So let's add one feature here. We're just going to add a function called setRoles that we're going to use to set the roles of the user. We're going to add that down here, and then up top in the AuthController, right now we'll just do var roles and just set it right there. Now in setRoles we will, actually we'll just take something in and just say, roles = role, and we'll just get rid of roles in these two functions. Alright, so basically now we have to have this extra step to set up the roles associated with the user. So that's pretty straightforward, now let's test this. Let's make sure that we can do this. Okay and now in each test we have to call authController.setRoles and set the roles the way we want them. And we've got to it down here, and this one is going to be user and admin, and then one more. Alright, so now let's test our code. We'll run this, hey, everything's still working. Okay, so we know that that's good, we know things are working, and we're making it. Now this is a huge violation of the DRY principle. We've got three different sets of code. Now one of them we do two things, one of them we're setting it differently, so for these two, let's add a hook. Now hooks are cool because hooks allow us to do things before everything.
Scoping in Hooks
Okay, so let's talk about this beforeEach and how it decides what to run. First of all if I take this beforeEach, right here, and move it down inside this describe, it's going to run this beforeEach before these two tests. But it's not going to run it before this one, because it's nested to the specific describe that you have. So let's run that. And now you'll see, it helps if I save it, that it failed actually. It just completely bombed. And here's why. You see it running the beforeEach before each of the authControllers, but it didn't run it before the isAuthorizedAsync. So things didn't get set and it didn't work. So let's pop that back up here, and now it's going to run before everything inside this describe. So it's going to run this test, this test, and this test out here. So that way it gives you some flexibility to do some things for some tests and some for other tests, before each test or just overall. And usually the before, just the before is going to be used when you're just doing global setup, like I'm setting up new things, like you're setting up your sandbox environment, and that, and then the beforeEach is really where most of your code is going to run because you want your tests to be more autonomous and not built on each other. So you want then set up new from one to the next. Okay, but we still have this basic Mocha test down here that's over actually in a different file. This starting.spec.js, that just doesn't do anything and so it passes. What we can do, if we want to run something before every test, there's actually an implied describe around everything. And so if I cut this, and I move that beforeEach up there, like that, now what you're going to get is this running beforeEach all four times. So there's the three and then the four. And so even though it's in a separate file, even though it's completely separate from everything else, because this beforeEach is outside the describes, it's going to run it for every test that it runs. Because it basically just takes that beforeEach and it adds it up to the global describe that covers all of the tests that Mocha is going to run. So just be aware of that, be aware that's how that works. Alright, let's save that, and then let's talk a little bit about troubleshooting our befores. Because really you want to keep your beforeEachs as specific as possible. And that might cause some problems, because you might end up with quite a few of these beforeEachs. So let's talk about how to troubleshoot where errors are happening.
When Hooks Go Bad
Alright, so let's talk about our hooks for just a second. I'm going to add another beforeEach that's actually only going to run down here. It's only going to run once, and it's actually going to throw an error. Alright, now let's run our test. Boom! And beforeEach hook should return false if not authorized, everything blows up. So there's only one beforeEach and so it's fairly easy to see, hey that, but let's come back up here, let's do it here. Let's run this hook again. AuthController before each hook for should return false if not authorized. Hey, it's still the same because our it, it's all weird. Like how do I know? And so especially if you've got quite a few, so there's a way to fix this problem. Right now this is an anonymous function, and it doesn't have to be an anonymous function. It can be a described function. We can actually give this function a name that describes what it's doing. So we'll call this one function settingUpRoles, and then this function has erroringFunction, and so now when I run it, now it says hey, AuthController before each hook erroringFunction for should return, and now I can pinpoint, hey, this beforeEach is the one that has the problem. You can even take it one step further, you don't have to just have it in a name, you could actually pass in a description. If the first parameter of the beforeEach is a description, you could just say this function is erroring intentionally. Now when I run this, look at that, AuthController before each hook this function is erroring intentionally, it should return false. So spend a little bit of time describing what's going on in your code so that when things break and when things happen, you understand hey, oh, the beforeEach that does this thing is the one that's breaking. And that makes it a little bit easier to troubleshoot all your beforeEachs as you go. Now we've got quite a few tests here. So let's continue to talk a little bit about troubleshooting and how to deal with documenting some things, or working with broken tests, and kind of how to deal with that.
Alright, so here let's look down at our isAuthorized, it should return false if not authorized, it should return true if authorized. I have a couple other tests that I potentially want to have in here. And I'm thinking about them now, I'm not thinking about them later. And I know that it should not allow a get if not authorized, and it should allow get if authorized. And I want to be able to just drop these two things in here, and know, Hey, I'm thinking about this now, Oh yeah, remember this for later. It should not allow a get if not authorized. And Mocha will let me do this. Mocha will just let me drop in it with the phrase in there without the function. And what's going to happen now is that when I run this test, let's go and get rid of our beforeEach that breaks, now if I run this test, I now have two pending tests. Hey, it should not allow a get if not authorized, and it should allow a get if authorized. And basically what this means is instead of what we used to do, it's just adding comments or adding ToDos, that kind of stuff, we can ignore all that, and we can just drop things in as we think about them. And I could say oh yeah, I remember this thing, now I've got to add this test too, and go ahead and put the it in there, and put the phrase in there, and so that now it'll just remind me hey, you've got a couple that you haven't done yet. And now you've got some pending tests. Nothing's failing, nothing's red because I haven't started working on it yet, but it's going to remind me, oh hey, you can go ahead and do this. Now this comes in handy in a couple of other ways. Say I'm working on this test and some of these other tests take a while. If you've done tests before in other environments and sometimes you could have hundreds of tests and all these tests are running, and every time you run your test it take minutes sometimes, and you don't want to wait. What you can do here is, let's say this describe, you can do a .only. And say, hey only run these two tests. And now notice you've got two passing and two pending, instead of four passing that you had before. Or I can come down here and only run this test. Now I should only have one test that's passing. And so I can focus only on the stuff that I'm working on, and nothing else, then I can get rid of the only and run that. Now sometimes you can go, so let's say hey, I do only here and then I come over here, and I do only there, and I save everything, and I run this, notice it's going to nest the onlys. So if everything that has the .only next to it, it's going to run. So then I could even do this, if I take that .only off, I come back to my auth.controller.spec, save that, here I've got my .only on this describe and then I'm going to do .only on this. So now when I run it, I'm running only that one, and then only the other one. So I'm going to run this because it's got a .only on it, but I'm going to ignore everything else, and then I'm going to run everything in this one. So there's just a way for you to isolate some tests. And one last example I want to give you is, I know you've never done this, but somebody else has checked in broken code, and you get the latest and now you've got tests that fail because somebody else broke the build, it wasn't you obviously, but somebody else did it. Normally what you do is you just come in and just comment that code out, and now your tests pass and you continue to work while you wait for somebody else to fix your build. This is a scary prospect because those tests went away. And if you check your code and those tests are gone because they're commented out, what you can do in Mocha, is you can actually skip tests. So I can come in here and say hey, this whole section is failing so I'm going to do a .skip, and I'm going to skip this section of code. The difference between doing a .skip and commenting out your code is that now when I run this, it's going to show should return false if not authorized as a pending test. It's just going to treat it the same way as these empty tests up here. But now they didn't go away. Those tests still exist, and they're still there, and they still show up when you run, but now they show up as pending so that your build can actually pass, and you can continue to work in your environment. So that's kind of how you can work with some broken stuff and you can focus on some things, and just use these .onlys and .skips to figure out the right way to do some stuff. One more quick thing to note about the .skip option, is that sometimes in our tests we have, we're doing environmentally specific things or something like that, and so inside this it you might have an if, let's just say something environmental, and then else we're going to run our test. So the problem with this thought is that if we skip the test, for some environmental reason, then it shows as passing, and it didn't pass, we just skipped it, and so what we can actually do is using the same skip mentality we can do a this.skip. If we don't want to run a test for whatever reason, we could just drop a this.skip inside the test, we could test for environmental issues or something, and then skip all the tests that aren't related to what we're trying to do. So the skip is super helpful to not swallow errors.
Alright, so we covered quite a bit over the course of this module, and we kind of dug deep into the mechanics of Mocha and how Mocha as a test runner, just kind of what does, and how it works. And we dug in quite a bit into how Mocha lays out its tests, and the describes, and the its, and all of those types of things. We talked about the beforeEach and the afterEach and all of those lifecycle hooks that allow you to control how things are created, and how they're not created. And we talked about how to manage what tests get executed, and with the .skip and the .only, and those add- on functions that we have to kind of run through everything. Alright, now that we have the mechanics, there's some clunkiness that we have still, and I want to help clean that up. So let's start a conversation about Chai and how to clean up our asserts so that things flow a little bit more cleanly.
BDD Style Assertions
Alright, so we've got a good understanding of Mocha and how Mocha works and what all the pieces are, we're not quite to the point where we're ready to just wholesale test an application, but we're getting much closer than we were just a minute ago. One thing I want to talk about in this module is our assertion style. We've been using assert., and that's kind of clunky, and we don't really like the way that that feels. We've got a very nice clean feel for our describes and our it shoulds, and all that kind of stuff, but assert is kind of clunky, and so I want to walk through using Chai to help clean up our assertions a little bit, and make them a little bit easier to read and just easier to follow so it flows a little bit better. Now Chai has two options for assertions and for BDD style assertions. They've got expect and should, and I don't care which one you use. And in fact, I'm going to kind of show you both side by side so you can see how that works and what that looks like, so that you can decide on your own which one you like better. And we're going to end with a talk about objects. So right now we've only been asserting whether or not something is true or false, and that doesn't really get us too far. We're going to kind of dig into assertions a little bit more so we can check on both values and objects to see how things work.
Alright, so we're going to be using Chai as our assertion library. Chaijs.com is where you can get much more information about it. We're going to cover quite a bit of should and expect, but there's always more stuff that we're not going to talk about, so here's where you would look. But we're going to follow the instructions, npm install chai, and so we'll do that right now. We're just going to go into our project folder and do npm install chai --save. And that's going to add into our package.json chai, right there, 3.5 is the version that we are using for this course.
Alright, now that we've got everything installed, let's talk about our asserts for a second, because up until now we have assert.equal, and we've been using this phrase. And it's kind of clunky, it just looks a little gross. I mean it works, it's functional, it's fine, but we're using this BDD style of Mocha, and so we have describe isAuthorized and it should return false if not authorized. We've got these nice verbose descriptions of what it is we're doing and how it works. And what I want to do is now implement an assertion framework that's a little bit better, and so what we're going to go with is expect for right now. We're going to talk about two. We'll talk about expect and should, but we'll start with expect. And expect is much more of a natural language expression. It just looks, expect something to something else. And we can change our assert.equal to this expect something, to do something, and that's a step in the right direction. It kind of gets us where we want to go with this BDD style. Now when we do expect is, expect something to.be, or expect something to.equal, expect something to.have. We have these phrases, these chains that we can use that get us closer to having much more of a natural language type style. What we would have instead of assert.equal, we have expect auth to.be.true. And that is a much cleaner, much nicer way to do this. And so if we come back over to our code, up here in our auth.controller, you can see right here we have assert.equal false, authController.isAuthorized admin. So let's pull in expect. So var expect = require chai .expect. Alright, now we have expect in here. And so instead of this, let's just go ahead and take one step. We'll clean this up just a little bit. And we can say var isAuth, for isAuthorized, = there, we'll just pull that out, and now we have assert.equal false and isAuth, just to separate it out a little bit. Now the way we're going to do this with expect is just this, expect, hey guess what, we're going to expect isAuth to.be.false. And that's how that works. So let's save that and we'll try it out. So run npm test, there everything's passing. And actually you can just say hey, expect that to.be.true. And now we run that and we see it's failing. So we can get rid of that, and so now this is a much more robust phrase. And we can build these out a couple ways, and we will throughout the course, we'll build these out in a couple more robust ways too, but expect gives us this thought. And this is kind of more what we're driving to when it comes to the BDD style of verbiage.
Natural Language Asserts with Should
The next step in that kind of progression, and it's going to be up to you which one of these you like better between expect or should, but is should, and should gives a much more clear, natural language style. So it's basically just something.should. Expect was expect auth to.be.true. In this case it's just something.should be something. And I like this a little bit better just because it reads much more cleanly and much more obvious what it is you're trying to accomplish. Now how this works is should adds itself to object.prototype, and when it adds itself to auto.prototype it appends itself onto the end of everything. So every object should have now should sitting at the back end of it. And it kind of just looks like this, something.should.be true, something.should.equal something, something.should.have something. And so you can kind of lay this out in a much more clean, nice way so that it's a little bit more easy to follow what's going on. So our example is going to come down to this, auth.should.be.true. And so when you put that into our test, the progression of the test becomes pretty easy and obvious. So let's look at this and let's see how this looks. Here we've got our expect. Just right underneath our expect let's add .should. Now one key importance between expect and should is that should is a function that has to be executed. So we have to execute this so that it will add itself to the end of object.prototype. If you don't execute it, you just get a function back, and that's not really what you want. That's the big difference between expect and should, expect isn't a function, should is a function and needs to be executed. So if we come down here let's do the second one. We have expect isAuth .to.be.false. Now in this case we'll just copy that line right there, take this out, we'll just take the whole thing out. In this case we want it to be true, so we're just going to say, isAuth.should.be.true. And you know, I like things to fail first. So let's do should.be.false so we can run our tests, see, we expected true to be false and we don't want that, we want it to be true. So that way you just know it actually is working. So if we flow through this, so we're describing isAuthorized, we're actually describing our AuthController, the isAuthorized function, it should return true is the user is authorized. So we pull back something and then we say Hey, isAuth.should.be.true. Very natural language, very easy flow there.
Dealing with Nulls
Alright, now we've got a problem with should. And if you just look at this code real quick, var iAmNull = null, iAmNull.should.not.exist. That test should pass. But iAmNull is not an object, therefore it doesn't have should associated with it, now we've got an issue, right? So expect iAmNull to not.exist, that works just fine, because you're passing null into a function. But let's talk about this and how we solve this problem using should. And we're going to stay in here, but I'm going to do exactly what we just had. So let's create a test, it should allow testing nulls that take a function. Alright, and then let's just take that code. Alright, var iAmNull = null, iAmNull.should.not.exist. Boom. When I run this, Cannot read property should of null. That is not helpful, because it kind of only tells me what's wrong. So now you know kind of what's going on. If I come back over to my spec, now this is where I'm actually going to need to pull this over. We have this variable should that we haven't been using up until now, because we're only using the should that's associated with the object.prototype. But now we're actually going to use this should variable that we have right here, because what should does is just sits out here, and now I could say, should.not.exist and we're going to pass in iAmNull. So it's not ideal, it doesn't look quite as awesome as it could. Boom, now it works. Or if you now make it exist, now it fails, because it expected it to not exist and it does. So should, in case of a last resort in things like this, you can just drop should out here to check whether or not something exists, or exception was thrown, or something like that. And this is kind of a fallback just in case we end up in that circumstance.
Working with Promises
Now up until now we haven't talked about promises at all. We had some async stuff, but it was all with callbacks. Let's take a look at promises now, and how we can work with them using Chai to have this still natural language feel to our test. Now in order to do this we're going to use something called Chai as promised. Let's go to our console and do an npm install --save chai-as-promised. Alright we run that, and now you can see in our package.json, we have chai-as-promised in there as well. Alright, if we go back to our auth.controller, here we had isAuthorizedAsync and we just returned with a callback. Let's duplicate this function. And we're going to return a new promise. And a promise makes up a function with resolve. And now instead of our callback, we're going to resolve and pass back true or false. Okay, so now I've got this promise coming back. If we go back to our tests, let's test isAuthorizedPromise. So we'll come down here to where we're doing isAuthorizedAsync, we'll just copy this, and do isAuthorizedPromise. Now the callback goes away, and now we just have auth.controller.isAuthorizedPromise, it's going to eventually return a result. And the way we're going to deal with that is authController.isAuthorizedPromise.should.eventually.be.true, and that's how we're going to test it. Now we're not going to do done, instead we're just going to return this promise, because Mocha is going to allow us to do that on its own. So Mocha's going to allow us to just return that promise and then it's going to pay attention to the promise all by itself. So authController.isAuthorizedPromise.should.eventually.be.true. And in this case it shouldn't be, this test will fail, but you know how I like to start with failing tests. Now that's one thing we have to do, is set this all up. So up here we do have to actually do require chai, require chai-as-promised, and then pull in kind of using middleware syntax chai.use chaiAsPromised. And then since we're using should, we've got to do one more thing, chai.should, and that's going to append it onto the end. Alright so now that we've got that, let's run this. Should return false if not authorized, expected false to be true. Hey, isAuthorizedPromise failed, because it should.eventually.be.false. And now everything's working. Alright, so that's Chai, kind of at the very simple level. There's more to Chai obviously that we can't cover everything, but that kind of gets us rolling, and gets us going to where we can start writing these natural language tests.
Alright, so that's Chai. And we started with this concept of assert and how it's kind of gross and clunky, and we talked about a different pieces of Chai, just so we can make some better assertions, or a better natural language way to do our assertions. We had expect, which was expect something to be something, and we had should, which is just something should. And we talked about kind of how both of those work, and it's going to be up to you. Do what you like, and figure out the one that works for you. I like should, but should does have a couple of drawbacks that we talked about as well. We also talked about verifying objects, and how we can delve into objects and test those as well. Alright, so that's that. We're almost there, almost to where we have all the pieces necessary to test everything that we want to test. There's one thing left that we need to do, and that's learn how to mock objects so that we can test things in an isolated way. So let's dig into Sinon for mocking.
Spys, Stubs, and Mocks
Up until now we've kept things pretty easy and pretty straightforward and unfortunately that's not the real world. In the real world things become very complicated, very, very quickly. And so what we're going to do in this module is start talking about how we mock objects. And what I mean when I say that is, we'll start passing objects around. And we need to keep track of functions on those objects and make sure that things are called when we expect them to. And when we're unit testing we don't necessarily want to test the other objet that we're passing in, we just want to isolate the thing we're dealing with. And so mocking objects is required to do some of that. Luckily for us we have this cool tool called Sinon that's going to handle all of this for us. And it becomes a pretty straightforward thing, not as straightforward as we might like, but we'll talk about it for just a little bit and we'll get down to the bottom of it, and how all of this stuff works. Let's take a minute, get everything installed, and paint the picture for what we're going to do, and then we'll get into mocking objects with Sinon.
Setting up Get Index
Alright, so I've added a new function to our auth.controller that's going to handle all of the auth requests for our application. And this is usually how we deal with Node applications. We've got inexpress, we pass controllers in, and so now we're doing getIndex. So when we're making a request to just auth/ this is what we're going to send it to. And I'm passing in two parameters here, req and res, so the request and the response. And all getIndex does is send a response to .render. And we now want to test this function. And so now I've got parameters I've got to pass in there, not just variables anymore, now they're objects, because both of these things are objects. And so we're going to work on how to make this happen. Now one thing, I didn't type it all in, make sure you add getIndex right here, to make sure that this thing is going to work for you. Alright, the first thing you're going to want to do is an npm install --save sinon. And that's going to get you installed with Sinon. And if we go to our package.json, just like I like to do, there's sinon 1.17.7; that's the version we're using for this. Alright. Now, when I start writing my test, we'll let's go in and let's add a test. So I come into auth.controller, so we're describing getIndex, and then we have our function. And our test is going to be it should render index. And then we have our function, and basically we're going to call authController.getIndex. And now we get to the crux of the issue. So here's what this whole module is going to be about, I need to pass things into getIndex. If we look back at auth.controller, I've got req and res that I've got to somehow create and pass in. So let's talk about that and how we're going to do that for the rest of this module.
Working with Spys
Watching Existing Functions
Alright, now spies are great for when you need a function, when you just need a random function to go and check and see that it's called. Those are great, but what if we already have a function? Say I've got a function that already exists and I want to do the same kind of spying on that function that we did just a minute ago. So let's change some things up a little bit and see how we would test a function that already exists. Alright, so in my auth.controller, if we go back over to the auth.controller, I've changed things up just a little bit. We've added this user here, and we can set the user by passing a user object in. And then I've changed isAuthorized to validate that the user is authorized by passing the role in. I basically just moved the functionality that was in isAuthorized out to a user. Now so we don't go too nuts, I'm going to simplify this just a little bit by creating the user just in my auth.controller, I'm not going to go create a model and all of that. We're just going to play around with that in here for just a minute. So in my auth.controller.spec, let's come down to isAuthorized and let's add here a user object. Now let's add a beforeEach that's actually going to create this user object. So here I've got a beforeEach and we've got a user, and these just have roles and isAuthorized. And this isAuthorized function is just the same function we had before, I just copied it over here. And then we're going to call authController.setUser. Now we set the user up up here so we still have access to it down inside our tests. Alright, now this test should pass. Actually let's do this, let's come down here to just this, or actually let's just do our describe. So using what we learned before, we're going to only run this describe, just so we don't get confused and run too much. Alright, see our tests are passing, everything's working. Alright, now we are going to start looking at how to look at this a little bit more, because see down here we have this sinon.spy, and we had res.rendercalledOnce.should.be.true, and I want to kind of do that same thing because it's possible I'm not even calling this user.isAuthorized. I changed my code to work with user, but it's possible this isAuthorized function's not calling user. So in this case, what we get to is use sinon's .spy, but it's going to actually let us watch a different function. So we can actually call sinon.spy on user and pass in isAuthorized as the function, and it's going to continue to let isAuthorized function the way it is, but now it's going to give me these details. And so that's the cool part about that. So down here back in our auth.controller.spec, after we create the user, but before we pass it in, we're just going to do sinon.spy user and isAuthorized. And then just so we can see what's going on, let's come back down here and actually just look at it. Clg user.isAuthorized. And just to keep everything simple, let's drop a .only on there, so we're only going to run this one test. Alright, let's run this. Okay now look, the whole function is there. Everything passed, the test passed, but there's our calledOnce is true, calledTwice is false, all of that's still here, but it still ran the function. The function still executed and it still worked. And so that's the power of a spy is we're just going to now check. So auth it should be true, and user.isAuthorized.calledOnce.should.be.true. We'll say that, and actually let's break it. We'll call it twice, run our test, hey, it fails. It expected false to be true. Pull that back out, now we're only running it once, and it passes. Excellent. So that's a spy. That's what spies are used for. They can either create dummy functions that we can pay attention to, or they can wrap existing functions and keep all the existing functionality, just wrap all of that and give us access to just that. But sometimes you don't want to do that. So let's say this isAuthorized goes off and calls a bunch of stuff. It's not whether or not they actually have the roles. What if we had something that actually goes to the database and gets a user? Well, maybe that's a problem. So let's look at something else, in this case stubs that are going to help us deal with that specific thing.
Alright, so what we've been looking up until now is we are just watching functions. So spies gave us this opportunity to just watch a function to see what happens with it. Sometimes we need to replace a function. In the last clip we basically wrapped a function and then passed it in, and we kind of got out of the unit test mentality. We were testing both the user object and the controller at the same time, and that's not really a unit test. So let's try and pull things apart, and in this case, let's start replacing functions. I just want to replace a function wholesale and work on it that way. And what we have for that is something called a stub. So sinon.stub is going to take a function and just replace it with something else. And what that's going to do is give us the opportunity to control the behavior a little bit, and make sure that it's working the way that we want it to work. And I'm going to return the things I want to return, or sometimes throw exceptions if I want to. So we can do it that way, it's going to be a little bit easier. So if we drop into our code, let's go, first of all, if you still have the .only from up here on this it, go ahead and take that off, I went ahead and took it off, but you go ahead and do that too, now I come back over to our auth.controller. So right down here in our res.render, if we're working through something that has your authorized to view it or not, this is our authController, maybe you have to be in an admin to look at our index, so let's say if req.user isAuthorized admin, then we're going to render index. And if you're not, we're going to render our error page. Alright, so here we've got this going. And let's see, there's a couple things we have to do. So now req has to have req.user associated with it, so we're going to pass that user object into our request. And then we want to stub. So before we were just spying on this function, I don't care about any of the role stuff, we were making that a maybe a little more complicated, but I wanted to show you spies. Let's get rid of all that and just stub this stuff out. So we'll save that, and then we're going to come over here, and let's do a beforeEach. Let's come up here and grab this beforeEach and come back down. Alright, so in our getIndex, we're going to have our user for getIndex and beforeEach user's going to be recreated with roles and isAuthorized. Now this might be a model or we might just mock something up, but in this case I don't isAuthorized to matter, but we're going to change this inside each thing. Alright, so it should render index, our request is going to have a user, and we're going to pass in user. But now we're going to say, var isAuth is going to = sinon.stub, and we're going to pass in user, and then the function isAuthorized. And we're going to say that it's going to return true. Should render index, let's just say that. It should render index if authorized. So we're stubbing out our isAuthorized function and it's always going to return true. So if that always returns true, then user is going to be authorized, and we should render index. So down here we should say the rest of this remains the same. I can check, so let's just say isAuth.calledOnce.should.be.true. There we go. So now let's test that. Let's run this test. Actually before we do, come back up so this describe of getIndex, let's drop our .only on there so we know we're only messing with this one test. Ah, expect false to be true. This thing broke, and we know why, because, and I kind of did this on purpose, because I wanted you guys to see all of these have to pass. Res.render, should only be calledOnce. Look at this, we're calling it twice. So there's two ways around this. Whenever you call res.render or something, a lot of times we'll just put a return there, so it'll only be called once. And you know that's a hard break. Hey, once I do a render, I'm done. Now we pass. Alright, and just so you can see what's happening, let's change our test. Hey, let's make our stub return false. Now it fails, because now I'm calling it with error. So the stubs going to completely get rid of this isAuthorized function. This isAuthorized function doesn't exist anymore. A spy kept it, and a spy executed it, a stub kills it, and it's not going to work anymore at that point. Now you can change this up a little bit, there's a whole bunch more things you can do. The one I'm going to do right now is, let's say there's an error. That's one of the reasons why I like using stubs, is because sometimes we do this. (Working) Here we're trying, and we're going to index or error, or let's just say notAuth, and then if an error occurs anywhere in here, then I'm going to do res.render error. Now with a stub, I can just say hey, sinon.stub throws. And now I am going to check. So getIndex isAuth.calledOnce.should.be.true, but your res.render should now be called with error. There you go. So we threw, if I change this back to index, boom, because we've got error instead of index. So that's kind of the purpose of stubs. The purpose of stubs is just get rid of this function all together, and let's have direct control over what a function does, so that we don't have to call out to other things, or maybe call out to the database, or we could throw errors, or we could throw types of errors. Because I could come in here and I could throw a specific error type. So let's throw an ObjNotDefined error, and I've just got to throw that specifically, those types of things. Now we'll use these in the next module to prevent us from going and talking to the database, which is going to be huge, and it's going to be super important. But I wanted to give you just an idea of kind of what a stub does and how it works, so that now we can plow forward and get something else done.
Alright, so in this conversation about Sinon, we've talked about spies, we've talked about stubs, now let's talk about mocks. And mocks are going to combine behaviors of all these things and then add one more. Let's look at this blurb from the Sinon documentation real quick. So it says, Mocks are fake methods, kind of like spies were, with pre-programmed behavior, kind of like stubs were, but they also add preprogrammed expectations, and that's where mocks become pretty cool for us, because we can kind of build out how we want something to work, and then just verify it at the end. Let's take a look at what that's going to look like. So we come back down here to our should render index if authorized. And so right now our test is failing, so we're going to change it to a .returns true. Let's make sure everything is passing. Alright, we've got our test, only the one, it's passing, and that's great, but look at all this stuff down here. We've got res.render.calledOnce, it should.be.true, res.render.firstCall should.equal index, all of these things, and I don't really want to deal with all of that stuff. And what mocks are going to do is kind of encapsulate our res object, and build all of these preprogrammed expectations, and then we're just going to check it when we're done. So let's look at what that looks like. So we're going to do var mock = sinon.mock, and we're going to mock res. So you actually mock the object. So we're just mocking the entire object. And then we're going to do expect something to happen. So mock.expect, so we're setting the expectations for our mock before we do anything. And so what do we expect for our mock? We expect it to render. It's going to be called .once, .withExactArgs, or you can do withArgs, but in this case you might as well be exact, index. That means it's not called with more, it's not called with less, it's called with index. Alright, now we can get rid of these two things. Boom. IsAuth.calledOnce.should.be.true, because we're checking our isAuth on our user just to make sure, but then we can do mock.verify. And say now just, so let's say twice, because let's fail our test so we know that it's working. Boom. Should render index if authorized, attempted to wrap render which is already spied on. Okay, now since we're doing this, actually now because I'm going to look at it differently, here we're going to just do a function. I don't want to spy on it twice. So we're just going to take that spy out, and now we're just function. Okay, so now let's run this. Boom. Should render index, unexpected render index twice called once. Called once with index, boom, there we go. So this is just a little cleaner, it's a little nicer. The mock wraps the object and we can set all kinds of different expectations on how this object's going to be interacted with. And then we called our controller and then we just do mock.verify and we're done, instead of doing a whole bunch of these should statements, because really, this looks cleaner than that. In this case we only have two, we could have four, we could have six, who knows, but in this case hey, you just have mock.verify and you're done. So that's the idea of a mock. There's more to it, and we'll talk about that a little bit more in the next module, but this is the basic idea. So that's great. We've talked about Sinon, we've talked about stubs in all of this, let's recap real quick, and then let's try and get this to apply in a much bigger environment.
So in this module we talked mocks, and we talked about how to mock our objects and our functions so that we can control them, because testing's not always straightforward. Sometimes we need to control some behaviors to make sure that we're testing the things that we need to test, without having to go through a lot of hoops and make things more complicated than they need to be. So we talked about three things. We talked about spies and how they're useful for watching methods, and not replacing methods necessarily, but just watching a method to make sure it's being called, it's being called the right way, at the right time, and all of that. We talked about stubs for when we want to replace objects wholesale. Let's just instead of executing this method, let's just replace it and say hey, here's what we want you to do. And then we talked about how to wrap all that behavior together into mocks, so we can have much easier expectations, and then we just call mock.verify and see whether everything's done or not. Alright, so we've walked through a whole bunch of stuff. We talked about Mocha, and we've talked about should, and we've talked about mocks, and stubs, and spies, and what comes in Sinon. We've talked about all of this. Now instead of keeping things simple, which we've tried to do up until now, let's pull an actual controller from my web apps course. Let's just take a controller from my web apps course, and then try and test that using the tools that we have now. And we're going to run into a couple of hiccups that you may have already thought of in your own code. And you say, well this doesn't work in this situation. Well, we're going to run into a couple of hiccups as we do that, so we can take what we've learned and then try and apply it and work through.
Testing Real Things
Alright, we've come quite a ways in this course, and we've talked about a lot of different things, we've talked about Mocha and Sinon, and all of those types of things, but everything up until now has been fairly contrived. And I'd like to take a break from contrived examples and start talking about reality, and talk about things that we'll actually run into every day as we're working. And testing is not always as straightforward as it's been made out, up until now. Sometimes we're dealing with modules that get imported, that I might not have access to, or databases and HTTP calls can be complicated. Databases because we're pulling in and we're using packages that are just imported, and HTTP calls dealing with streams, and we haven't talked about streams and how to mock streams yet. So we might have to deal with that. So let's take a look at some realistic scenarios. They're not quite full blown because I still don't want to get overblown with everything, but let's take a look at some real scenarios that deal with some real things, in a little bit of a controlled environment, so we can understand how you put all of these tools together in practice.
So I've created a contrived example that will help us to understand what's going on. And I've created this example called getViewer that has basically, when you go to /git/jonathanfmills, or whatever name you want, it goes out to GitHub and it pulls their information. So here's my public information on GitHub, and then here's my repos, all of the repos associated with my profile. And so this is the way that we're going to look at how to deal with some more specific use cases. So I have this app called GITCHECKER that has a service. And this service basically goes out, https, out to api/github.com, and pulls back all of the users and then all of the repos, when it's done we do getRepos. And this presents an issue, because look, how do I mock this? I've got on data, on end, I want to disconnect this a little bit from its dependence on the API, so I can work offline maybe, and so that's a challenge that we have to solve. How do I deal with HTTP streams as I'm doing this? Also in my gitController I have gitService, and I want to test my gitController, but I don't have access necessarily. See how I'm executing this here, means I can't just go get ahold of the gitService that gitController has to mock it out. That's a pain point. And so there's a couple of things in here that are all real world things you'll run into in the real world, it's not all nice and cut and dry in the real world. So we've got a couple of things that I'm going to dig through in this module to show you how to deal with some different types of things. This code is available out on GitHub. If you go out to the GitHub repository for this course, you can pull this code down, and then we can get started with it.
Setting up Tests
Alright, so the first thing we want to test is our gitService, because that's going to be the building block for everything else that happens. So we'll start by testing here. If you look at the package.json that I have included, there's an npm test script that just runs Mocha in the tests directory, and you can call it the spec directory if you want to, it doesn't really matter. So I'm going to create a new folder called tests. And this is where we're going to put our tests. And we can put everything under there however we want to, because we've got the star star, but let's create a new folder called services. And then we'll create a new test, so a new file called, gitService.spec.js. And we called it .spec.js coming back over to the package.json, notice we're looking for anything that's .spec. So any file that's .spec Mocha is going to run. And so once we get in here, now we start doing the heavy lifting and the fun in this whole thing. So we can run this just by setting up a basic. I want to start just, let's just execute the service, and see what we get. So let's do a describe our GitService. And inside our describe, let's see, our GitService, if we look at it, when you call getUser it should actually return right here, the user with a repos associated with it. So it gets the user from GitHub, it goes and gets the repos from GitHub, it appends the repos to the user. So let's just say hey, let's describe GetUser, it should return user and repos. Alright, now in order to interact with our service, we actually need to include the service. So let's go pull the service, so require.., gets us up to tests, .. gets us up all the way, services/gitService. So let's start by just executing it. So gitService.getUser, we'll pass in jonathanfmills, and when it comes back, let's see, it'll return a user, and let's just, let's just print that user out and see what we get. Now since we're returning a promise, our .then, we've got a couple of options as to how to let Mocha know to wait for everything to come back. The easiest way, since we're dealing with promises, is to just drop a return on there. So we're returning back the promise to let it know, hey, this thing is coming. Alright, so let's run this. Let's just npm test, and see what we get. Look at that, there's our user. Everything's there, there's a lot to go through. So obviously printing it out to the console is not the greatest thing, but hey, this thing worked. Now it's possible for you it didn't work, and part of that is because look at this big red number right here, 1367. Remember that sometimes things timeout in Mocha. Mocha's got a set timeout limit, and so we can fix that. If it's not working, if you're getting a timeout, just put a this.timeout. And I set it to 10,000 ms, and that should be plenty of time. If it's not, you can up that if you're timing out. And that's going to be completely up to your environment. So now that we know that's working, we know our gitService works and is sending me back a user, but I don't know if it's the right user, I don't know if it has the repos property, and obviously I don't want to look through the console to do it. And so we should have talked about enough up to this point to kind of get you going, but let's pull in a couple of things so we can make sure we know what's going on. So let's start with chai, and then let's go ahead and pull in sinon, even though we're not going to use it quite yet, we will here pretty soon, so we might as well go ahead and get it done. And then once we have all of that done, let's come down here in our console, instead of doing console.log, let's actually test some things. So let's say hey, user.login.should.equal. Let's just say jon, because we know that's wrong, and then I like to get a failed test. You may have seen this theme over the course of this course. And remember in order to get should, we've got to execute it, so chai.should, and that's going to add this should to prototype for everything. So let's save that, and then run. Hey, looked it failed. We expected jonathanfmills to equal jon. So that's what we expected, and now it works. And see, we've got a couple of things spitting out here. If you go to gitService, I've got a couple of extra console.logs in here we can just kind of take out, to clean that up if you want to. Alright, so we're testing that, but it said, should return to user and repos. So let's test to see if I've got my repos in there too. So user.should, so how do we do this, right? I want to know if user has the property of repos on it. User.should.have.property.rep, because that's wrong. Hey, and I failed, and now I succeeded. Alright, so we actually have a test. It's a full-blown integration test from the service to the back end, because we're hitting the service and we're testing it. It's an integration test from the service, backwards. A full-blown integration test would be including the front end, we're not there, but we've got this working. But I want to add some nuance to this, so let's talk about how to mock up this HTTP call. Because the struggle is, if you look at gitService, I'm just making right here, I'm doing https.request. So https is a core package. How do I do that? So let's talk about how to mock that up in Sinon, so we can test this a little bit better.
Mocking Second Call
We have mocked the first call, the first HTTP call in our gitService here. And we commented out this second call, because I didn't want to overcomplicate it the first time, but let's put this back in now. So now we're making a second call that should be returning a second thing. And so let's look at how to do that. There's a couple pieces we want to do. The first one being, we need to create a repos response. So here we had our GitHub response, but then if I click on repos right here, I now get this massive list of repos. And actually I don't want to pull all of them, so I'll just pull the first one. I know that that's an array. And so let's come in here to our gitService.spec and say hey, we'll create a repoJson and drop that in there. And we'll minimize that one just to fold it up. So I've got my gitJson and my repoJson, and now what I want to do is say hey, the second time a call happens, I want this PassThrough to happen for my repoJson. So let's just do the same thing here. Let's create a new repo, let's say repoResponse creates a new PassThrough. And then repoResponse.write JSON.stringify my repoJson, and then repoResponse.end. Alright, that gives me a repoResponse and a GitHub response; now I have two separate responses. But I only have one call here, and so this.request.callsArgWith1 gitResponse. But I wanted to do something different on its second call. And we actually have the ability to do that with a stub or with a spy. We can actually do onFirstCall, we're going to do this. But on the second call we're going to do something different. So let's do callsArgWith, still the first argument that's the callback, and we're going to do repoResponse, and it still returns a new PassThrough. So now the first time this stub request is called it's going to send the gitResponse, the second time it's called it's going to send the repoResponse. So let's see if it's doing that. So user.should.have.property repos. Let's pull that back out. I'm still going to log it out so we can see whether it looks the way we expect it to. Okay, so everything worked, and look what we sent back. So here's my login, the way we expect it, and then here's my repo, just the one, and everything worked. So I can kind of chain these all through, onFirstCall, onSecondCall, and so on and so forth, sending different responses back based on what we want to do. Now this is only a little useful right now, because all we're checking to see is if things came back together. Really what we should be testing, is making sure that my call is being made appropriately. So when I call gitUser with jonathanfmills, my http.request, the path should be users/jonathanfmills. I should be using the right thing. So let's play with that a little bit in the next clip, and see how we can test that.
So we've got mock HTTP calls going and we're doing a couple of different calls, and all that seems to be working, but what I want to do is I want to validate some things about these calls to make sure everything's working. And one example is, right here in my gitService, if these headers aren't here, if this User-Agent header isn't there, then this whole thing doesn't work. So let's see how we can get a hold of the options associated with these HTTP calls to see if we can validate that some of this stuff is being done properly. So if we come back over to our spec, notice that we've got our onFirstCall and our onSecondCall, and what we want to do is validate the arguments that are being passed. And the way we do that is I can come in here and actually get a hold of the parameters. And for right now we'll actually just pull the parameters so that we can just look at them a little bit. So let's go var params = this.request.getCall 0.args. And what this is going to give us is the args associated with that getCall, except it won't. And now we run into a problem, cannot read property of getCall of undefined. And the reason why this is undefined gets into a much deeper conversation that's outside the scope of this course, but I'll talk about it for just a second. Line 153, this no longer, since it's inside a callback, no longer references the same this that everything else does. And we get around that usually by using arrow functions. And the reason why we can't use arrow functions in general in Mocha, we talked about this in the Mocha module, is I have this this.timeout reference. And so we want to avoid using arrow functions for the most part. But I can use arrow functions down here in my then, in order to avoid this particular problem. So let's fix that. And now I have access to this.request.getCall sub args, and then actually let's print it out, so you can see what's going on here. Alright, so here it is right here, I've got my hosts of my options, which is what we wanted, and then the callback function. And so if we go and we look at gitService, getUser, the first time it's called, https.request, we have options, which is this, and that's what we saw printed out, and our callback. And so that's what we have here. And so what we can do in our spec is we can actually do params sub 0., let's see what do we want to do? Headers, let's see, what do we want? We want to make sure that headers sub User-Agent = gitExample. So let's actually look at how that comes across here. Let's just see what that looks like. So now if I run this, I get User-Agent and gitExample. Now can we just get that? And we can. And so see there, I can now look just to get an example. And I could say, params.headers User-Agent .should.equal arg gitExample. Now I know if this call happens without my User-Agent, I have a test that fails. And usually you'd break some of these out to separate tests so that you're not having too much happen in one test, and we're starting to have a lot happen in this test. But I just want to cover kind of the things that you should be looking for, right? So now I can actually pull the params of the FirstCall, so call 0, pull the arguments out of that, and then start to test them and see and make sure that things are working the way we want it to. Now if I wanted to do the SecondCall, so the SecondCall should actually come out of gitRepos, should also have this User-Agent gitExample. But the path, let's just say hey, the path should.equal this. So let's test that. So down here I should be able to say this.request.getCall sub 1, so the second call, .args. sub 0.path. So sub 0 gives me the options, then we go down to path.should.equal, and then this should be whatever got passed in to begin with, jonathanfmills. So if getUsers jonathanfmills, it should be called with jonathanfmills. If I change this to jonathanmills, now when I run my test, it fails, because notice I got jonathanmills and I expected jonathanfmills. If I change it, now my tests pass. So that's one example of some real world stuff, where I can get a hold of https up here in my spec, and start to test and mock out parts of the core Node. So I just mocked https.requests, and everything else just used that mock request.
Testing the Controller
Alright, so let's take a step up one layer and start looking at our controller. And this controller, there's only a few things. First it gets an instance of gitService, and then it actually creates an instance of gitService; that's what the parenthesis on the end here mean. If we look at gitService, all gitService is is a function, and that function needs to be executed in order to provide the object, this object, getUser, down here at the bottom. So once it has an instance of gitService, it just sets up a function called userGet, that's called by our router, that calls gitService.getUser after pulling the userId out of our parameters. So let's test this. And we'll do the same thing we did over on the gitService test. Let's start just testing end to end. So let's wrap our controller, then we can kind of walk through and see how we go from there. So let's start by right-click on Test and creating a new folder called controllers, and then we'll right-click on controllers and create a new file called gitController.spec.js. Now what we're going to want to do here, is we're going to want to get an instance of our gitController. So var gitController = require../../controllers/gitController. And then the same as before, we need to execute that to get an instance of our gitController. Because again, gitController's just a function that's not being executed down below. A lot of times you'll see these being executed here and returning back the same thing. I'm intentionally not doing that here, because that's going to throw a wrench into what we're trying to accomplish, and I'm doing that intentionally. Because you're not always going to have that, and so I want to show you how to deal with that a little bit. So don't change that to make your test easier because I'm going to show you something. Hold on, stick with me. Alright, so let's start setting up our test. And in this case we're going to do our describe, and we are describing gitController. And in our gitController we're going to say, it should get user and repos from our git service. Alright, so once we have that, let's talk about what we want to test for just a little bit. So the first thing we can test is we can just see if the whole things works, right? So I'm going to, let's just call gitController.userGet and see what happens. We can test the whole thing end-to-end, so more of an integration-style test. And we have to pass in two things, we have to pass in the request and the response. So since this is going to be async, let's come up here and say done. Alright, now request. Request ultimately is going to come down to, let's look at our gitController again, we're pulling req.params.userId out of our request. So let's see if that'll just work. So let's just create this dummy req.params.userId that = jonathanfmills; I can pass that in. Now the response isn't quite as easy, because this is where we're going to actually have to do our work. Because if we look at the way controller works, notice res.Json is how this whole thing ends. So it's going to call this json function; that's how the whole thing concludes. And so we need to create this thing called res as an object, and this object has a JSON function, and that function, let's just call it test, because this is the test. This is where everything's going to go, and then we'll just create our function. Now we expect test to be called with user, and we expect user to, let's just say user.login.should.equal jonathanfmills, just like before. Now we haven't set up anything up here, so let's just run through that stuff real quick. So we need chai, and then we need to execute should so that we have access to all of that. Alright, let's save that, and let's just run this, and let's see what we're going to get. But actually before we do that, don't forget right here we have to call done, to let the test know that we're done. Now when I run Mocha, it's actually going to run everything. So it's going to run this spec down here, and it's going to run this spec. And the way around that, if you remember, because I only want to worry about this right now, I can do a .only and then it will only run this one function. So let's run this npm test, and I'm timing out. So remember how we deal with that. We have this this.timeout that we can use to fix this. There we go. This timeout went a little bit faster, 1200 ms, but it all worked. And let's just kind of make sure. If we change the req params, run this thing again, now it should fail, and it did. Alright, now we get to the tricky part. Because really I want to make sure that some things are happening, because I said it should getUser from the gitService, but I have no idea if that actually has occurred. I just know that I'm sending it a param, userId, and I'm getting something back. And so I actually want to pay attention to my gitService, and see if I'm actually getting back what I think I'm getting back, and if it's calling it the way it should be. And the way we do that is by spying on things. So let's pull in sinon, and then what we want to do is spy on our service. Now here's where the trouble starts. Because if we go into our gitController, we have this gitService that is its own thing. So these parenthesis here mean that it returned a function and I executed it. So I can't get a hold of this the way I did HTTP. If you remember from our gitService we pulled https. I can't do that here because I returned a function and that function was executed, so I can't get a hold of that cleanly. Our other issue is our gitController.spec also just returned a function. So I can't really do much with either of those things. And so what I need to do now is pull in another package. And so let's do an npm install rewire, and what rewire is going to allow us to is deal with this kind of issue that we have. And what we're going to do is we're actually going to separate this out into two commands. So let's pull this out, and let's get rid of that. And just call, let's say, we'll just call GitCtrl. Alright, and so what this has done is it's just broken out the execution of the function. That's really straightforward and easy to do, because this now, gitController, this big gitController, is the function; it's basically this thing right here that has this gitService that's pulled back and then this function. And then we have this gitController down here, that is the execution that results of that function, which is going to be this object, this userGet object. Alright, so now what we can do is instead of using require, we're going to use rewire. And what rewire is going to allow me to do is reach into my gitController, this big gitController object, and modify this gitService. Not the required gitService, not the function that comes back, but the actual gitService thing that gitController has in-hand right here. So now I can start to modify that. And that's what we want to do in order to look at what's going on. So let's come down inside our gitController describe, and let's do a beforeEach. So in my beforeEach, we're going to create a function, and I'm going to try and pull out of this gitController my gitService. And we're going to use this __get__ to pull gitService out. And basically what this does is even though this is not exposed, so if we come back to our gitController, this gitService is not exposed anywhere, well, it's var gitService. I, still using rewire, can go in and I can pull it out, and what that's going to let me do is if we come back to my spec, I can actually come back here and say hey, this.getUser is going to be sinon.spy, my gitService, my getUser. So I've now added a spy to my gitService. And that becomes pretty powerful, because that means that now I can look at this getUser and see what's going on. And then the last piece of this is I've got to set it back with __set. So now what I have in this whole thing is access to this.getUser to see what's going on with it. And so if I come down here into my test, let's just say hey, getUser is my, my gitService getUser.getCall 0, let's just look at what is being called, and this should be this., let's look at what's being called here and see what's happening. Actually this. won't work, if you recall, because this is all a callback that's done later. So let's pull that back out, let's actually just do it this way. I'll do it differently than I did before, just so you can see another option. If I create this up here, now I'm just using, it's a global variable, which isn't awesome, but this is a way you'll see it happen a lot of times, just to deal with the whole this. thing. Alright, let's run this. Although before we run it, let's come back up here, we forgot one very important thing. We can't just start using rewire without actually using rewire. So var rewire, there you go. Alright, now you can see, look at that, the args. So let's come back here, let's look at what's going on. We are, getUser, so gitService.getUser, the first time it's called, which should be the only time it's called, .args, so it's called with jonathanmills. If we come back in here, we change this, now it should pass. There you go. But jonathanfmills, right there. So what we can do is this, so getUser.getCall 0 .args, the first argument should.equal jonathanfmills. And guess what? We can go one further and say, getUser.calledOnce.should.be.true, because we only want to call it once. If we call it twice then it shouldn't work. There we go, everything's passing. So using rewire, I've been able to pull out something that wasn't available to me, and get and set everything around it, so that now I can spy or mock or whatever something so I can deal with it correctly. If I wanted to from this point, I could mock, I could do a sinon.stub for gitService, and replace everything. And that would look very similar to what we did in the last clip with our gitService.spec, where we are stubbing out HTTP. Alright, so now I've got some tests. I've got my Controller.spec, I've got my Service.spec, let's talk for just a minute about how to know whether or not my tests really even mean anything, or whether I'm testing enough to know that my code is good.
Code Coverage with Istanbul
So let's end this course with a look at how to check to see whether your tests are testing enough stuff. So let's look at Istanbul, and how we can get code coverage information based on that. Alright, so if we look at our gitController, let's clean up a couple of things. Let's take this .only off, just to make sure we're getting everything. And then let's come to the command line for a second. The first thing we want to do is an npm install -g istanbul. So Istanbul is a code coverage tool that's going to generate some reports for us, to let us know whether or not we're testing enough stuff. And the way we run Istanbul is like this, so we do istanbul cover, and cover just means hey, let's look at code coverage, and then we're going to, because of some issues, point to this mocha right here. You can't run it with just istanbul mocha, you run it this way. And then the --, and then wherever our tests are. So our tests are test/**/*. That's where we stuck our tests and so that's where we're going to run this. And so I'm going to hit Enter on this, and it's going to run our tests, gitController, gitService, everything ran. And it wrote some code coverage information. And you'll see this coverage thing popped up over here with a report with a whole bunch of stuff in it. And if you pull this HTML up, you get something that looks like this. And for us it's only a couple of things, for some it'll be a huge amount of stuff. But if I click on, let's say I click on controllers, my gitController.js, you see look at all this stuff that's tested. And this is all it's tested. If I go to my services, and my gitService, notice there's one line that's not tested. So we don't ever throw an error when we're checking our gitService. And so that http, all that stuff we did, we never mocked out an error. So that lets us know hey, this is a potential scenario. And notice, we're just doing console.log here, so we might want to fix that, right? If we come in here, let's change one thing, let's comment out, or not comment out, but let's draw up a .only on our getUser, and we'll run this again. We'll pull this up, and let's come back here, now you see hey, our controller, none of this stuff is getting called. We never actually execute userGet. So that's a problem, right? Because that test never runs we never execute this code, so this is a good way for us to know what's being covered, what's not being covered, and figure out if we're doing things the right way.
Alright, so there we have it. That's quite a bit of coverage of Mocha, and how to start this journey of testing your code. And I'm going to end with this thought, right, testing is an art, it's not a science. There's a lot of different opinions, there's a lot of different ways to do it. What I wanted to do with this course is equip you will the tools. And then there's some other courses out there that will give you an idea of some testing patterns, or some thoughts around testing. And I encourage you to go watch some of those to build on top of these things that we've talked about. We talked about Mocha. Mocha is the test runner. It's kind of the engine that runs everything, and gets everything going, and then we threw stuff inside it. We talked about Chai. Instead of doing assert., Chai gives us this opportunity to do things like something should equal something else, or just kind of those nice common language elements to our asserts. We talked about Sinon for spies and stubs. And there's a lot more there in Sinon, and you can go out and look at Sinon's documentation, but for the most part, this was what you're going to use, spies, stubs; spies keep the existing functionality intact, stubs just kind of throw everything else away. And then we did rewire for changing an object and reaching into an object and allowing us to change some things that really in theory, we shouldn't be able to change. And then we ended with a conversation on Istanbul to let us know what we missed, and what other opportunities we have to get some testing done. And this was just kind of a quick run through Mocha. There's more here and there's a lot of stuff, but like I said, there's some other courses you could go and you could check out to kind of dig deeper into many of these topics.
Released4 May 2017