What do you want to learn?
Skip to main content
Node.js Testing Strategies
by Rob Conery
Tips and Techniques for creating simple, elegant tests with NodeJS and Mocha.
Start CourseBookmarkAdd to Channel
Table of contents
Setting up Webstorm
A First Test
Well now that we have our tools installed and we have our IDE picked, let's structure things up and write our very first real test. So I'm going to be using sublime text. Let's open that up from the command line, and here it is. If you're curious, I am using the programmer theme here and I am using the flat land dark syntax color scheme. In addition, I'm using Inconsolata as my font. Now I say all this because a lot of people have asked me, what do you use for your color scheme and theme and so on? Alright, well, as you can see here, I've got the rocket-shop directory open. Inside of it we have Node modules, we have tests, we have git ignore, package.json, and .idea. That is from WebStorm and geez, wasn't I just saying how much I liked WebStorm? Yeah, we wanna get that thing right out of there. It is only settings and so on, but yeah, I don't really want it. Okay, I'm gonna delete the entire text directory because I am not going to be working here inside of the main rocket-shop directory, because all we are doing is we are building the subscription system. So for this I'm going to create a lib directory and I'm going to make a subscriptions directory inside of lib. Node works with modules and that's what I wanna do. I wanna have a module dedicated specifically to subscriptions. In fact, I am even going to use npm to help me create a package.json because all the dependencies of our subscriptions module I want to be isolated. In other words, rocket-shop itself doesn't need to have reference to Stripe or any of the other things I might need inside of subscriptions. So I'm gonna have this be as isolated as I can. There we are. So we have a subscription module for rocket-shop. We have a package.json. Very good! Okay, well let's keep going and I'll structure up our subscriptions module in a fairly standard way. Inside of here I want to be able to have dedicated models and processes that I can use in an isolated fashion, so I'll create a directory for each thing, models and processes. Now there are different ways of doing this. However, you will find that you'll come up with a way that you like to separate your code. I've kinda followed along with what I've seen other Node projects do and what feels good to me as a developer. Okay, well there we are. There is the structure of our project, lib. Inside of lib we have subscriptions and then we have models and processes and a dedicated package.json. Now for the best part. A dedicated test directory specifically for subscriptions. I don't like monolithic projects. This is one reason I love Node. You can isolate everything into tight little packages including the tests. That to me makes all the difference and it's gonna keep things really light weight for us. What does that even mean? Well, let's open up our module now in Sublime Text and take a look at its structure. It's pretty simple to look at. Models, processes, and tests. Nothing else. If we had the entire rocket-shop project open here in front of us, we would have a lot of files to think about and here we just have our subscriptions module, which is going to keep us focused. Speaking of, let's get off the ground here with a very typical happy path, kind of spec. Here I'm saying that I want to describe what happens when a new user signs up. Here I'm just gonna call it news or sign up as a feature and my scenario is gonna be, what happens when everything works? In other words, what happens when the user gives us a valid email, first name, and last name? Well, I expect them to be let in somehow. I don't know, but the point is I got some expectations down here on screen and I don't have any assertions. What's Mocha gonna do? Well, here you can see it's in blue. It's pending. In other words, it didn't see any assertions so it said, well, if you don't have an assertion, this is the test you're gonna write in the furuter and that is correct. Getting off the ground is always the hard part, so let's think about this a bit more. New user signup? Well, it's not really a feature is it? How about applying for a mission? Keep in mind here that you can change your mind however you like, at any time. The important thing is that you use clear naming and things make sense. So instead of sign-up spec, I'm gonna call this mission application spec, because I'm trying to describe how people are going to actually apply to get in and go to Mars. Naming things is hard and you're gonna see me change up a number of times, but the important thing is that you start somewhere. This is a good start for us and in the next module, I'm going to start writing out tests and we'll get rocket-shop off the ground, so to speak. Sorry. In this module we got things set up for ourselves. We took a quick look at IDEs and tools and then we installed the things that we are going to need like Mocha, Sinon, Moment, and maybe CoffeeScript too, if you're a person that likes CoffeeScript. Then we set up WebStorm to use Mocha and took at look at some of WebStorm's interesting features. Finally, we structured our project as a module and ran our very first test.
Simple Tests with Node.js and Mocha
The Membership Application
In this module we're gonna do simple testing with Node and Mocha and we'll start out pretty simply, just doing the basic stuff, but don't worry. It's going to get complex as time goes on here. So we'll start out doing very simple unit tests and then we're gonna move on to testing with dates using Moment JS. Then we'll get into behavioral testing using a process workflow. Then we're gonna get into things that are a little bit more complex. Specifically we're gonna wanna know if certain callbacks are being called and also if certain methods are firing in our processes. Then we're gonna handle some Asynchronous code. We're gonna start with an event emitter and then I'm gonna move on to the Async library. But for now, let's focus on a simple unit test, nothing tricky. We have a membership application that we need to create, so let's just focus on the basics. Let's revisit the very first scenario that I wanna work with here, which is applying for a mission, using all valid information. Now I wrote originally isn't terribly clear. I wrote email first and last, but we also have height, age, and weight that we need to consider and it lets them in, well, what does that even mean. Well, let's rewrite this. When working with features and scenarios, our assertions should hearken back to what we expect the application or the code under test to do, given a certain scenario. So in this case, when we submit an application with a valid email, first, last, height, etc., etc., what do we expect to happen? We would expect that application to be valid and we expect it to report that the email is valid and that the height, age, weight, and name are valid as well. And this is an important thing. Right from get go here, what is it? Well, it should be obvious when we run our test and see the output, so in this case, it is our feature under test. In other words, applying for a mission, and the scenario that we're trying to test here is what happens when we give all valid information? Email, first, last, height. Well, what happens with that application? Well it is valid and it reports a valid email, height, age, weight, name, etc. Well, alright. Well all these tests are blue, which means they are pending so let's change that and here I'll create a brand new file for our membership application. This is gonna be a simple Node module and I'm gonna use the prototype here and just export that prototype directly. So I'll save this as membershipapplication.js and at this point I've got a number of patterns I can use, but let's go with this simple constructor pattern and I will define some methods on our membership application prototype that will allow me to validate certain things because that's what I said would do in my test. Here I wanna test if the email, height, age, and weight are valid and then I will have an all-in-one routine, if you will. This is valid, and it'll just return the results of email, height, age, and so on, the individual tests. Now you might be wondering two things at this point. Why do I have all these individual methods, and why am I putting the validations here inside of the application itself? Well the first answer is actually pretty easy. There's a lot of functionality that's gonna go into each one of these validations. I wanna keep things clean. I don't wanna have one massive validate method. It doesn't make much sense. I'd rather have things done individually. Moreover, this is gonna help us later on using the Sinon JS library and our spies and I'll show you that in just a bit. Well, the other question is, why am I putting this all on the application? And the simple answer for that is, I like to follow tell, don't ask. This is more of a guideline than a rule, but the idea here is that the logic should stay as close to the data as possible and you want to basically tell an object to do something with its methods. Instead of asking, hey, what's your email, first, last, etc., you basically tell it to validate itself and it does. In the Smalltalk in Ruby world they think of this as receiving a message and they think of methods on an object as, well message receivers. This differs slightly from a classical understanding of methods on objects that are kind of treated like verbs. In other words, if you had a class or an object named dog, you see the classic example of, say hi, and then you see bark, and that is a typical classic example of a method on an object. In the Ruby and Smalltalk world, you could also use a method to send a message to that object, like walk with me or heel or sit. Anyway, that's the idea here behind tell, don't ask. You wanna tell an object to do something and you do so through its methods. Okay, so back to the code. At this point I am going to be passing in some information through args and I'll need to set properties on the application itself and I've got a lot of work to do here because, well I might have undefined things and so on and I'm gonna have required stuff that I need on my object. For this, let's use Node's assert library. This isn't just for testing. You can use it all over your code. When you expect something to come in, then you can assert that it should be there, in this case first and last. If it's not there, then an exception will be thrown and in this case, I have to scratch my head a little bit. Is that what I want? Do I wanna really stop execution if I don't have a first and last name? I don't think so. Let's take this out and instead I'm going to use Underscore. I'd rather not be that strict, at least not yet until I know what I'm doing. So I'm gonna install Underscore using npm install underscore. Now if you don't know what this is, it's a utility library that allows you to do all kinds of things. In this case, I'm using the extend method on Underscore and that's gonna take all of the properties and methods on args and graft them on to my membership application. That'll be nice for all the properties that I send in. So then let's fill out the validations here for email. I'm simply checking that email exists and there's an at signal in the email string. Alright, the next thing we need to do is fill out height, age, and weight validations. You gotta remember, we are taking rockets into space and there are certain height limitations as well as age limitations. I know, please don't send me email. These are just rules that were handed to me from the Rocket Shop, what can I say? We also have weight restrictions because, well, as you know, weight and fuel, they go hand in hand. Alright, looking good. Not classic TDD or BDD for that matter. We wrote our expectations here as we should've done, but I didn't exactly watch everything fail and do the red green refactor. That's okay. As you'll see, I'm not dogmatic about this stuff. And again, I wanna state that this is not about theory and testing theory, this whole course. This is more about the tools and how you use them in Node. How you put them together and write your tests is up to you. This is the way I do it and hopefully you can see that there will be differences, probably, between the way you do it and I do it.
The Membership Application, Part 2
We've written the skeleton of our simple unit test for our membership application. Now let's get in there and make these tests pass. The first thing I wanna do is I want to have a variable that represents the state of the thing under test I want to have that created in a before block. The before block in our test here in Mocha, goes off before everything else does in this block. So this is going to arrange the object that I'm going to test. I don't wanna have to keep firing all the logic down below. I'd rather one variable here represent the result of our scenario. If you find yourself changing that variable or working with a new one down below, consider creating a brand new block for it, because that usually means you have a whole different scenario. Alright, well here we go. We have a valid application that we're setting up here in our before block. We have initialized the variable above so we can test it down below. So this is how you do this in Mocha. You just use a callback and that callback should contain some kind of assertion. Mocha is going to look at this and see if it passes and if it does, it'll give it a nice green checkmark. Speaking of, let's see if we can get one of those green checkmarks. Here I'll just assert that our app is valid and if it's not, then I'm gonna give the assertion library here a message and it's that easy. Assert or assert.ok, those are synonymous. Before I run this I want you to take a look at the way the code is laid out here. It's very simple. It's easy to read. I don't have a lot of blocks. I have a number of assertions down below. Your eyes can easily scan this page. This is important. When you start building out your spec files, things are going to get complex. You're gonna have a lot of tests to wade through. What you really want is the simplest possible thing that you can do. This is why I don't like to take a lot of dependencies on other libraries. I don't wanna have a lot of strange syntax. Here I'm keeping this bleedingly simple. Of course, that's gonna change, but that is going to be the goal because what I'm gonna drive for here is clarity. I want my test to look the same, pretty much, on the code page here as they do when I run them, and speaking of, let's do that now, and I'll flip over to the console and run Mocha and first, yay! Second. Read this from top to bottom. Applying for a mission using valid email, first, last, height, age, weight is valid! It kind of reads okay, but the idea here is I'm going for as much clarity as I can so I'm gonna be doing this back and forth where I read things to myself, go back to the code and tweak it. I'm gonna change a lot of the wording here to be as clear as I possibly can. So for now, let's focus back on our validations. I'm going to pop out an assertion here for email and for height and weight and name, and I'll go and I'll just fill these in. Height is valid, age is valid, weight is valid, and name is valid. Now if any one of these things failed at some point in the future, well then we'd know our is valid function failed for a particular reason. Oh boy, and I run into a problem, and it looks like our final test here, valid name, looks like it doesn't exist. So heading back over to the code, sure enough it doesn't. I had thought that I put that in there, and I didn't. Oh boy. Well, that's okay. It's only gonna take just a second. I wanna be sure that our first and our last names exist for the application, so let's go back and we can try this again and re-running it. Mocha, boom! All green checkmarks. That's exciting! Functionally, I'm happy about this, however, not terribly happy about the way this reads. Let's read it together. Applying for a mission using valid email, first, last, height, age, weight is valid. Reports a valid email. Hmmm. There's a lot of repetition. Reports A, reports valid, what does that even mean? I could dig this thing to pieces. I think I'm gonna rethink this a bit. Let's go back in here, applying for a mission. Let's just say membership application requirements because that's what we're dealing with and here we're dealing with validations and here let's be specific. Application is valid if all the validators return true. Much clearer. Let's go on down the list here and see if we can tweak this to reflect exactly what our tests are doing and what it is we are asserting. (typing) Oh, much better. Two things I really like about this. The first, it reads well. It reads as if you're reading top to bottom or sort of like a check list. The second thing I like about it is age assertion is one line. It's easy to read and that makes me feel as if I'm not trying to do too much. Alright, well going over this again, there's still some tweaks we can do here. A little redundancy. All validators successful. I think that makes a little bit better sense. Let's tweak this here. I want this to be very specific. So let's have it say application valid if, all validators successful, that makes good sense. Okay, now let's run this. I like it. Application requirements. Its valid if, all validators successful, email is 4 or more characters and contains an @. Okay, you might thing I'm being a little bit nutty about this, but remember, you're going to be reading this an awful lot. It should make sense to you. If it doesn't, then you might be doing the wrong thing. Now we can output this, if we want, in HTML format. All you have to do is pass in --reporter doc to Mocha and then it'll output HTML as you see here. I could output this into a file and if I wanna be a complete nerd, I can take that file, print it out, and take it to the meeting. Here it is. I can show my boss. Look at! This is how everything is going. Ah, geez, I did that once and I got laughed out of the meeting; I did, really, I am not kidding. I took my little check list here, I printed it out, took it into a meeting, and said, look at what I've been doing! And they just, they laughed at me and they said, whatever Rob, so I suppose I have to pass that along to you. It might sound like fun, but you don't wanna print this out and take it to a meeting; you will get laughed at.
The Sad Path
In the last clip we focused on what happens when everything goes right. Well, that is not exactly reality, is it? Well, we can make sure that that is valid by now working on the sad path. What happens when things go wrong? So this first block you can consider to be, well acceptance test. It should always work. When everything goes right, you wanna make sure your app always behaves exactly the way it's supposed to. Well, on this second block here, what we're gonna do is throw stones at that, so what happens if, in other words our application is invalid if email is 4 characters or less, and so this, of course, should not be valid. So we are going to set our validapp.email to d@d and that is going to violate our length. For those of you familiar with Mocha and Node and you're looking at this saying, ah, what are you doing? Yes, this is not exactly a good thing to do. I will talk about why in just a second. For now, I just wanna see if this will work. So here I've just reset the email on our valid app and I want to assert that the email is not valid. So let's do that and our test passes. It might not look like there's a problem here, but I violated one of the things and I said not to do above and that specifically is I am changing a test variable from a different block and that could have disastrous consequences later on. In fact, you will see me back myself into the woods in a later module. For now, what I want to do is I want to not do this. I want to make sure that I have variable specific to each block here. In this case I'm just going to drop it right inside of my spec. I don't need a before block for this because this is a simple test all in and of itself. So here I'm just gonna create a brand new application with a bad email and make sure that it's not valid, and in fact, it is not. Our validators expect the email to be greater than 3 characters. Okay, the next thing I wanna do is I want to make sure it has an at sigil in it and for this I'm going to once again create a new application and there are a number of ways I could format these tests, but it's simplest inside of here to just create a brand new application for each expectation. But didn't I just say you should have a variable specific to each describe block that's under test? Well, I did, but in this case my golden rule of simplicity trumps everything. Well, I'm gonna let that have free reign. It's just easier to create a brand new membership application each time. That way I can read it clearly and I can be certain that it is going to have its own instance that is under test. So that's what I've done and as you can see, I've got a lot of tests here and I'm testing each one of the validators. Let's take a look at how it runs, and it runs really well. So look at this. The application's valid if, and you can read it really well. It's invalid if, and it basically shows the opposite of what's above it, which brings something to mind. Why do I need both test cases? It's almost like saying black is not white and white is not black. Hey, both those things are true. Well, really, that's kind of ridiculous. It's also a bit redundant so let's just go up here and have our blanket validator tested and make sure that the application's valid in that case and, you can see, it is valid, and by definition if the email or height or age or weight are anything other than valid, it's gonna be invalid. So in this case we've covered everything nicely. No code is the best code and that includes tests.
Testing Dates with Moment
Testing Process Behaviors
As I keep saying, I'm not necessarily dogmatic about how I do things when it comes to testing, but I do really like BDD or behavior driven design, especially when you're putting together process oriented code. So in this case, I need to create the review process for when an application comes in and somebody or in this case, our application reviews it to make sure that everything is okay. This is typically a stepwise process with lots of steps involved and that can lead to some really ugly Christmas tree code. So what I like to do is use Node's event emitter in a structure that you see here. I require the emitter and then I inherit the emitter down below with Node's util library. The next thing I do is I write myself some comments inside of the class itself and what I expect to happen. Here I need to make sure the app is valid. I need to find the next mission for the person who's applying. I need to make sure that the role that they've selected is available, and height and weight is right for that role, and I need to accept the app with a nice message or deny the app with a nice message. I know that each step in this process could change and I might be adding steps, which is likely, and I will probably be taking them away. So while I'm here, I'm just gonna code it. Why not? And this is kind of breaking the dogmatic TDD or BDD the way I'm doing things, but I'm here and it's easy to write. So for this I'm just gonna emit that the application's validated. This is gonna fire Node's event emitter and pass along the app variable. If it's not valid, it's gonna emit the invalid event and it's gonna just pass along a message saying there's a problem with this application. Simple enough. However, it would be nice to know what's going wrong here with the application so, while I'm thinking about it, might as well do it. So this.validation message, this is just going to interrogate the application itself and it's going to figure out what's going wrong and if it's valid, then I'm going to say it's valid. Otherwise I'm going to ask each one of the validation methods, hey are you the one that's false, and well, you get the idea. I'll just return an individualized message. This is going to allow me to use this in my tests. Hooray! Look at that! Nice and clear. Alright, well heading back to review.js, instead of just saying, there's something wrong, I can now call this. Validation message. And moving along, so for find the next mission, I don't really know what I'm gonna be doing here, so let's just stub this thing out and what I will do is I will just create an object that represents what I think a mission will be and we'll have a commander and pilot, an MAV pilot and some passengers, and so for now let's just emit that the mission has been selected and again, I'll pass along the app. Alright, well moving along here, we need to make sure that the role is available and again, why not just stub it out? That's what I'm gonna do and I have no idea what is going to happen about a role or, I don't know, how to select one, so I need more info from the client. So for this case I am simply going to emit that the role is available. And as far as height, weight, age, and being right for the role, well, can you guess what I'm gonna do here? I have no idea and you might be thinking, dude, what are you doing? You're writing a bunch of functions that aren't really doing anything and yes, you are correct. That's okay. To me this is part of the design process, the thinking process. Not only is it important to get the real code out there, but it's also important to think about how it's all gonna go together. In this case I know I have to do these things above and I know that eventually I'm either gonna accept or deny the application, so I might as well just get it on down and make sure that what I'm thinking here in terms of how this is all gonna go together, will actually work. Alright, well we have the mid points and the endpoints of our process. We also need the place where it's gonna start and that will be process application and once this happens, I'm gonna emit an event, basically saying the application was received. Here I'll take a callback as well, next, and that is something I'm going to need to remember, because I am writing things in an event-based way inside of here, but I'm going to need to make sure that I have a way to call back code later on. So to do this, what I'm gonna do is inside this review process instance, I'm gonna create a callback variable and then down below here, I'm going to set that callback variable equal to next. There you go and I should point out here that that should be one line above. Line 53 should actually be on line 52. That'll cause an error. I'll fix this later on. So what I'm gonna do with that callback is I'm gonna actually use it if the application's accepted. If it is accepted, I am gonna return an object that has success true in the message that you see here, and again with deny application, I'll do the same thing. I'm gonna call back. I'm not gonna pass along an error and I'm gonna call back a data object that says success is false and I'm gonna give a message. Where does that message come from? Well, it is passed into the deny application function. Okay. What am I doing here? You might thinking, Rob, have you lost your mind? You haven't really written any code. Well, what you might be sensing here is that I have a bit of a harness going on. Since I'm using Node's event emitter, it's really important to me that I wire it together correctly. In other words, this is kind of structure and glue that I'm putting together right now and I have found that it is much easier to make sure that you get this event chain done correctly before you have any code in here. Okay, so what does that even mean? Well, let's take a look at this. This is our event path or our event chain. When the process application method is called, it's going to emit an event called application received. When that event goes off, that is going to fire a method called insure app valid. When that happens successfully, the validated event is gonna fire and then that's gonna call find next mission and if the next mission is found, well, you get the idea. That is our event path. Well, what if things go wrong? If any of these methods have a problem, it's going to emit the invalid event and it's going to pass along a message and that is our sad path down below. This code looks good to me and when I run my first test, it should all pass, which is exactly what I want. If it doesn't pass or I have trouble, it's going to be specifically because my event chain here isn't put together the right way and I'll need to know that up front. Speaking of, let's write some specs here and see if we can get this thing to work as we intend. So in our review spec here, I will do what I have done for every one of our test files and I will use Node's assert library. I'll pull in the review process module that I just created and also our membership application. Here I'm gonna describe the review process. That is our application feature. Simple enough. The next thing I wanna describe is something basic and simple. What happens when we receive a valid application? And the next thing I need to do is create my before block, as you've seen me do, and inside the before block I am going to create a valid app and for that I'll borrow some code from our membership application tests. Here I'm gonna do something a little bit different. Here I am passing in done to the callback for our before function. That is gonna mark this as an Asynchronous test. This test needs to be Asynchronous because it's going to be working with Asynchronous code. Specifically, when I call process application. It expects a callback. When that callback comes back, I'm gonna wanna fire done so I can tell Mocha that I am done with this before process. Let's just see how it works. Here I'm gonna create a new instance of the review process and then I'm gonna call process application, sending in our valid app. Then I'm gonna expect back some error or a result, hopefully a result, and then I'm gonna set my decision variable to that result and finally, I'm gonna call done. Once done is called, Mocha will continue with execution of our tests. Speaking of, what tests are we even gonna run? Well, let's start with the most basic one. I mentioned before, I just wanna test to make sure that we have a successful return. This is gonna tell me that I've put together the event emitter stuff in the right way, so I'm just gonna assert, decision.success, and if it doesn't happen right, then I'll have a look at the returned message. Okay, well I'm almost done. I've just gotta be sure that review process, there we go, instantiation happens inside the before block, not outside of it. That make everything crash, that would not be good. So let's do a quick review here. I have a feature above, the review process. This is a feature of my application. Underneath that I am describing a scenario, which is receiving a valid application. I am constructing that scenario in the before block and the representation of this scenario is kept in my decision variable right there, and then down below, I am specifying what the behavior of the application should be using assertions. Look at that! I did it right the first time. Oh, that's no fun. I was hoping for something to go wrong so I could troubleshoot it. Alright, well our event chain is working nicely and we have our scenario put together. Let's move on and see if we can break things.
There are a lot of steps in our review process and we're gonna wanna make sure each one of those steps is carried out. For that we can use the spy functionality from Sinon JS. So to install it I'm gonna use npm, just npm install sinon, and then I'm gonna save this as a developer dependency. Trying to explain how Sinon works can be a little difficult so I've found that the easiest thing is to just show it. So let's do that. Here I'll just require Sinon straight away, right at the very top. I wanna use the spy functionality. In other words, I want to know if something is fired on a particular object. For this, all I've got to do is to create a spy and for that I'll just create a variable called spy and then I'll tell Sinon to spy on my valid app object and then in, as a string here, I'm gonna say spy on email is valid. So let's put all of that information up here at the very, very top. These are just variables that we'll wanna use below in our before function. So here I am spying on our email validator and everything is working and that's good. We didn't get any exceptions. Now let's see if the spy is actually gonna work. Here I'll just assert that the spy was called. This means the email is valid method was called on valid app. There it is. So I declare the spy above saying sinon.spy on our valid app object, and make sure that email is valid is called. Down below in the review process it is in fact called, so everything works. Great! Things are working so let's go and add another assertion here and this way I can be sure that our review process indeed validates email, just by making sure that that spy was called. Let's rerun our test. Hooray! We have a nice new assertion. Well, I don't need to use spy.called here in my assertion. I can use a little bit of syntax goodness given to us by Sinon. When you tell Sinon to spy on something, it will actually wrap that object and its functions so you can interrogate those functions. As you see here, I could just write validApp.emailIsValid.called and it will tell me if that function was called. Spying on our valid app object doesn't make all that much sense. What I'd rather do is I'd rather spy on the review process itself and as I mentioned, there are lots of steps in this process and I wanna make sure each one of those steps goes off. So let's change things around. Here I'll put the instance of the review process in the spy and then, having a look at our event path, let's just start with the very first one, ensureAppValid. Let's make sure that that is called. So as you just saw, this should just work because it is being called we know that, we're getting success back. So down below I'll just say review.ensureAppValid is called. And boom! Hmmm. Well what is happening here? Well, this can cause a lot of problems. We are getting back false. In other words, Sinon spy is telling us, nope, it is not being called and the simple reason is, because I'm using events. For some reason, it doesn't work that way with Sinon. You have to have a process that is calling a function directly. It can't be the result of an event trigger. Now I don't know why that is, but if you're using events like I am, you have to do things a bit differently, so here all I gotta do is I have to wire up something that happens when validated is called on my review object. In this case I'm gonna call spy directly and I'm gonna get in trouble for it. Hmmm. This is just something you have to know about responding events. You don't wanna actually invoke the method you wanna use or the function. You simply want to specify this as the function to be called and you leave that to the event emitter to do it. Agggh. Alright, well let's try it again, and ha-ha! Still getting problems. Well now down below here I have to change this to spy.called because our object isn't wrapped. Oh boy! There we go. This works and that's how you work with events in Sinon spy. It doesn't feel very good, but let's continue on here. I wanna spy on each one of these events, so what I will do is I will just wire up a bunch of spies, and there you are. So every time the review fires each one of those events, I'm gonna call spy. So how do I make sure that all of these are called? Well, one thing I can do is I can change spy.called to call count and do an assert.equals. So I wanna make sure that that spy was called, oh, let's say four times. That's it and above it's called, should be called four times because four events go off. And that works, but as you're probably sensing, that's not the ideal situation here. While it's nice that it ensures that it's valid, it doesn't tell us which one is firing and if one of them is not, then we wouldn't really know. So let's create individual spies for each one. We have a validation spy, mission spy, role available spy, and so on. Now I just create new assertions for each one of those spies to make sure that they are called and that's all I need to know and if they are called and everything returns successfully above and my first assertion returns success, well then I know that each one of my validators is being fired and everything is working as I expect. Let's try it. It works.
Having introduced Sinon into the equation here, we're starting to feel a little bit of pain in terms of using an event emitter. Let's change that around using the Async library. As I mentioned, tests are gonna lead us to using tools, in this case Sinon, and that's gonna lead us to patterns in considering our design. In this case, using Sinon to spy on an event chain, and that's not the easiest thing. There's probably a better way and that's why tools and tests push us into different patterns and ways of doing things, so let's see what that would look like. In here, I'm gonna remove all of this event stuff. It really isn't using Sinon or spying to its best advantage. It actually made me write a lot more code and I don't really feel good about it and when you're writing more code to support testing, that's a bit of a pain. So what I'd rather do is I'd rather do something a little bit more direct, using the Async library. So here I'm going to just commit my changes and here I'm just adding a little thing about spying and I am going to make sure that I merge in everything I just did because I'm going to check out a brand new branch using the Async library in my review process. This is important. When you make a big change like this, please be sure that you do it inside of a branch. Alright, well let's plug in Async and I've already installed it using npm and a --save. If you don't know what Async is, let's head over to the repo and take a look at it. Basically, it allows you to work with a bunch of asynchronous functions in an organized way. There are two methods that are core. There's parallel and there's also series. There's another one called map, but I'm not gonna deal with that right now. If you wanna know more, head over to the GitHub address that you see above. So, parallel will execute a bunch of functions all at the same time and then give you a single callback to respond to. Series will fire things one at a time. I have to change the way I'm working with the application or the membership application. I'm gonna work on an instance level variable here, so I wanna make sure that I have a dedicated review process instance for every application that needs to be processed so that means I'm gonna use a constructor. Here I just wanna assert that an application was given to us and if it was, then everything will be okay. Well, since I'm working with an instance level variable called app here, this is a membership application, I need to change the way that I'm actually calling each one of the steps in the process. So instead of passing the app along, I'm just gonna pass a callback and that's a traditional Node kind of callback with an error and any data pass along. I can also remove the process endpoints here, accept application and deny application. I don't need them anymore, but I do need process application. I can remove the event chaining. This is great. I am removing code. I think Async might just have to stay. Wow. That's always good man, when you can remove code because you're using a certain tool. Hurrah! Well here I'm not emitting, I just need to call next. In this case if the app is valid, I'm just gonna call next. There's no error, I'll just pass along the app. Same thing here. If there is an error, I'm gonna pass that error along directly and that will be the validation message, and there will be no data to go along with it. This is a very typical Node call structure right here. If's there an error, you pass it along in the first argument. If there's any data, you pass it along in the second. There's no trickery here. There's no event emitting, nothing. Alright, well that's it for process application I just need to call that directly. I don't need to pass in the app, but what I do need to do is I need to set up Async and I'm gonna call async.series. In other words, I'm going to give you a bunch of functions, execute them one after the other. Each one of those functions will be called; however, one of them might have an error. If it does return an error, then this callback will be called directly and I'll be able to handle that error as I'm doing here. If there is an error, I'll just say success is false and then I will pass along any error message. Notice I am not returning the error itself. I don't wanna throw. I don't wanna have any assertions trip on this. Down below, in this case of success, I'm passing along null as the first argument. That would be any error that happened. Of course, there are none. And then second, I'm just going to say that success is true and welcome to Mars! Okay, well let's string together the functions that we're gonna call and they're just one after the other that you've seen above. So the first one that I wanna call is ensure role compatible. And the next one I'll call here directly is, find next mission and role is available. This is all the stuff that you saw me do in the event chain, but in this case I'm just passing the functions in an array and they will be executed in the order of the array. My next task here is to deal with all this event stuff and spying. I'm gonna take it all out for right now because I just changed a bunch of stuff around. I'm gonna skip all of the spy tests down below. I just wanna make sure that it returns success as I expect. And then, I just need to pass along the membership application in the constructor because I changed the way the API worked, if you recall. Alright, well that looks pretty good. Let's give this a whirl and see if I did things right. Nope. Oh boy. I wish I could spell. You know, sometimes I just get going so fast and I start refactoring, I misspell words like it. And well, almost, okay, can't read property is valid of undefined. Hmmm. Well, let's see. It looks like we have a bit of a problem here, again, just naming of an argument. It should be args.application not app, and wow! Look at that, it worked! Yay! Well, you know what? A few small spelling errors, that doesn't really count. It's almost as if I practiced these demos. Crazy! Okay, now that I've done this we can go back and we can set up our spy the way it should be. So here I'm gonna spy on review and ensure app valid. This is what I wanted to do in the first place before. This time, however, I can call things directly and do things in a simpler fashion. So down below here, I can just say review.ensureAppValid.called. I don't need to interrogate a separate variable like I did before. So I can change each assertion down below here by interrogating the review object and the function directly, making sure that it was called with .called. That looks pretty good. Let's take off those skips and go back and run our test. Do you think it's gonna work? Let's see, and it does. That makes me happy. Let's go back and take a look at our code. Take a look at that. It's nice and clean. Single assertions, they make sense. This is what you're going for. You're going for something that feels good and there's no other way I can put it. Do your tests feel good? Well, there's more we can do here, let's see. Async allows us to work with rays of functions as you see here, but there's a cleaner way. Let's take a look at that. So the first thing I wanna do is I wanna create a brand new method called approve application. That's gonna take a callback, just like the rest of our steps above, and for right now, I'm just gonna pass along true without an error. Why would I do this? Well down below here we are using a kind of brute force way of working with Async, passing in an array of functions. I don't need to do that. I can actually name these functions if I want by passing in an object. Each one of these keys is gonna be passed back to us. The value of each one of these keys is gonna be the return value of the function, if it doesn't cause an error. So success is going to be true, in this case. How can I use this? Well, you can just pass results along as you see me do here. Now let's be sure to pass along our message as well. I don't need to set a success flag, because, as you can see here, success is being set by Async using the approve application function. So let's take a look at what this looks like. I'll console.log it to make sure I did it right. Well, not really. I've got a problem here. There's a lot of extra stuff here. It looks like I'm not passing back the data I need to pass back. That's because I forgot to change things around. Specifically, you can see I'm passing the app back here. I should be passing the result of role compatible. It should be probably true, I guess. Either that or some kind of role. I think for now I can probably just set it to true and I'll do the same above. Role is available. I'll just pass back true and here instead of setting the mission variable on the app, I'm just gonna pass back the mission itself. Man! Things are getting really easy. So app valid, I'll pass back true as well. So let's log the results and I want to see the message output as well to make sure everything is formatted right. Look at that. Man! Things just got so easy all of a sudden. I love that. Just by simply using a different approach. Specifically, I removed the event emitter in favor of something a little simpler, which was to have a review process instance powered by Async. In this case, our tests and tools pushed us to a different pattern that actually made life a lot easier and hopefully this is gonna help our design, which will help our future tests, and you'll see. In this module we got started with simple unit testing and set up the structure of our module using a very basic pattern. We started testing our ideas, working with dates and Moment.js and then we got into a behavior driven design, BDD, with our review process. And finally as you just saw, we cleaned things up with Async and Sinon helping us out.
Data Access Considerations
A Look at Node.js ORMs
Stubbing with SinonJS
Setting up a Repository
Repository pattern is a very old pattern. A lot of people don't really like it, especially in the .NET space because it tends to get out of control rather quickly, but since we're doing things modularly with Node, it tends to fit really well. So before if you recall, we were stubbing the find method and it doesn't really make much sense. Let's get explicit and make this thing a repository and if you notice here, I'm trying to get the current mission. That doesn't make much sense. Let's rename it to get mission by launch date and that way I can send in a launch date that I would expect. In addition, we need to have something that inserts a mission here so let's create a method that's explicit. Create next mission. Very nice. I always like things when they're a little bit more explicit. Okay, get mission by launch date. For this we just need to pass in the date. You don't need to have any arguments that can be changed so let's be explicit. We expect a launch date, and there we go. (typing) And if we don't find a mission, I'll need to create a new one and so I'll use the create next mission method on our new repository. Alright, well let's go back over to our tests. Now I just have to stub that new method and see if it works. Running our tests. Yep! However, if you've been watching closely, then you'll notice that, well, this test is really not doing what it says. My stub is yielding an object with an idea of 1. That is not testing to see if the mission is created if it doesn't exist. It does exist. Our stub says it does. Well, you can back yourself into problems easily this way. Well, if we change it to null, which means the mission doesn't exist, well, we have a time out problem. That is because we are not stubbing create next mission. It is being called and that callback is not firing. So that means we need a new stub. That means that we need to create the next mission and in here, we can put a new mission and just leave it empty for now. It would've been really easy for me to miss that. Sometimes mocking and stubbing can be a great benefit, but other times if you don't quite pay attention to what you're doing, you can back yourself into a corner. I'm gonna talk about this a lot later on. For now, here is a properly mocked DB repository and that makes me happy. The repository pattern, as I mentioned before, is very powerful, but if you try and use it over a large code base, man the thing can spiral out of control and the next thing you know, you have overlapping repository methods. In our case, since we're using the module pattern of Node, well, it keeps things kind of small and contained, so I am okay using the repository pattern here. In fact, I think it fits quite nicely. Alright, well with all of this talking, I forgot to run my test to see if it would actually work. Let's do that. Whoops. Well, it looks like I forgot to reference our mission module. We can do that really fast. Drop in the mission, and let's run this again. Yay! Everything works as we'd expect and I feel a little bit more confident about these tests this time. Well, we're almost finished with stubs, but let's take a look at one of the best parts. Down below here, notice the code that I can write. I wanna be sure that get mission by launch date is called so I'm going to do it in the same way that I used the spy before and it works. And this is something to remember about Sinon. Spies are the first level. Stubs are kind of the second. They're just like spies, but they actually return something. The fact is, you can see here I used it exactly as a spy. In fact, having this functionality exist is going to help me write a few more tests. This is good tooling. I like it. Okay, well let's rename our feature to mission planning and then we'll have two scenarios here. No current mission and then if we do have a current mission, I wanna test both possibilities. We have the first scenario covered already with our existing stubs, so what I'll do is I will just copy this down below and put it in line and here I'll just return a new mission when get mission by launch date is called, so I'll just re-stub our DB instance. But I wanna be sure that something comes back that I expect, so rather than just returning a brand new mission, how about I return an object with an ID of 1000? That way I can return mission 1000 down below and I can have a better test. So running this, we've got a problem. Attempted to run, get mission by launch date. Oh boy. It turns out with Sinon that you can't actually wrap a method twice. Moreover, if you take a look at the test I actually wrote, I am just testing that my mocking framework works. It doesn't really do much for me, does it? Ohhh. I'm not exactly happy about this, but let's solve one problem at a time. Sinon is choking on this because it doesn't want to magically decide which stub to use when. So just because I stubbed it in that before block, well Sinon doesn't know that, so it's not gonna make that choice for you. Instead, it's just gonna throw if you're trying to do it twice. It's trying to tell me gently, dude, you're doing it wrong. So there's a way to unwrap it and that's by using restore on my wrapped object. Solving problems with more code. Ooof. I don't like this. I'll fix it later on. For now, let's just see if that is actually gonna work, and it does. Now I just need to go back and tweak my assertions, so down below, I'm asserting that it returns mission 1000 so let's make sure we do an assert.equals here and we'll say currentmission.id is 1000. Well, at least it works. That's a good thing, but have a look at this. Returns mission 1000, and I'm asserting that the current mission ID is 1000, so that's validating that my mocking framework is indeed working, and down below we're making sure that get mission by launch date was called. Well, that's good too, but this test right here smells really bad and as I said before, you wanna feel good about the tests you write. Looking at this, I just don't feel very good about it. I'll have to fix that.
Making Things Easier with Helpers
When Helpers Hurt
Every time you use a tool to help you out, whether it's stubbing with Sinon or drying things up with helpers, you gotta be sure you don't abuse things. So looking at this, well we've got a lot of green checkmarks. What could possibly be wrong? That's kind of fun. Why would there be problems? Well, the problem isn't with my code necessarily, it's with me. It's easy when you start working with helpers to say, you know, I've got a lot of duplication in my code, I really should try and dry it all up, and for instance, take a look at this. We are doing some stubbing with our database and it would be easy to think well, geez, let's put this in a helper because I'm probably going to need to use this database instance again later on. So here I'll just move all the stuff into a helper. It makes perfect sense. Why not just export a stub to database? That way I don't have to keep doing it over and over again in my code. If I wanna have things be flexible, which I do, I can pass in some arguments. Those arguments can contain special return values if I want. So for instance, if I want to return a specific kind of mission, I can pass that in in the arguments. Otherwise, I can just return a new mission as you see here. Well, that looks pretty good. What could possibly be the problem? Look at all the code I'm about to save. So let's take out all of this stuff and I'm just gonna say helpers.stubDB. There it is. Well, things look better, don't they? Let's run this and see what happens. It works! See, this is misleading isn't it? You can only see the problem if I come over here to our review spec and decide to reuse our helpers. So let's instance up our mission control one more time, and for this, of course, I'll need to pass in a DB, so let's ask our helpers for the stub DB. Now you might be thinking at this point, why are you doing all this? Well, our mission control needs to be instanced and I'll need to pass that DB into that instance because I need to pass mission control into our review process. Again, I'm using dependency injection here and that review process is gonna need to use mission control when we ensure that the role is compatible and when we accept the application as well, so I'm just preparing things. Well, taking a look at this a little bit longer, if you're a fan of dependency injection, you're starting to see something happen here, which is that your dependencies sometimes become rather large and this can quickly become a very big pain in the butt, because as your processes grow and your injections grow, things become harder and harder to do. So people have responded by creating things called inversion of control containers. Nate Kohari, a friend of mine, created one of the most well-known ones for .NET called Ninject and he also created one for Node called Forge. So if you're into dependency injection and wanna use inversion control, it's there, I just wanted to show you that. For me, right now, I think we're okay. Speaking of, now that I've wired this up, let's run our test just to be sure nothing is broken. Oh, man! Let's take a look at this error. Attempted to wrap get mission by launch date, which is already wrapped. I didn't really do anything other than, oh yeah, I decided to move the instantiation and stubbing of our database down into a helper. Can you see what the problem is? In short, the problem is that our repository is a singleton and we are trying to stub a singleton. This is our tool set telling us there's a problem. That problem came to light because I'm trying to be efficient here by using helpers. So a bunch of things have gone wrong all at once. This kind of stuff drives me insane because I've, one, tried to get too tricky, and two, I've kinda been using the wrong pattern all along and I didn't really notice it. This is the kind of thing that just drives me bonkers, but for now, let's see if we can fix this, because maybe what I can do here is I can just see if our stub has already been wrapped. This is just not a good idea. So if you take a look at our rough method here, create next mission, it has all these properties including this one here called display name. What I could do is I could just see if this thing has already been wrapped. We could use an if statement here, db.createNextMission.displayName, see if it exists. If it does exist, well then don't wrap it again. If you're looking at this and you're saying, Rob, what are you doing? Yes, I will fix it, I promise you. I'm just gonna show you that it works. Why am I showing you that it works? Because it is so tempting to chase bad code with more bad code. Here I've actually made my test suite work with a failed solution and it's so easy to back yourself into this hole without thinking clearly about how you got here in the first place. So let's take this out of our helpers. In general, it's a good idea to keep the stubs and mocks that you create in the test suite in which you're using them, so I'm just backing my way all the way back of here. In fact, let's here put this in a before block so we're clear about how and when we're using it. There, that makes me feel a little bit better. And let's move the mission control variable above here, so it's being set and we can use it down below. Okay, that makes me feel a little bit better. Things aren't failing and I don't have that ridiculous code checking to see if something is wrapped.
Listening to Your Tools
Let's continue refactoring here and we're gonna actually run into some trouble. Let's listen to our tools and do things the right way. What I mean by that is, here is our review process. It makes more sense to just pass in a database here and then the review process can access the modules that it needs. In this case it needs to use mission control so I'll just reference that directly right here and then, I will new up the mission control down below and I'll just pass in the database that was passed into our review process. There. That makes me feel a bit better and we have an error, as kind of we'd expect. I'm not passing a database instance in to our review process; let's take care of that now. And while I'm in here, let's clean things up a little bit. I don't have a before block where I am working with the variables and spies and so on so let's do that really quickly. There we go. So here I have all my variables declared at the top. Decision, review, validApp, and I'm sending that to the Helpers.validApplication. In the before block here I'm creating a new review process and I'll need to pass in the DB instance when I do that. I should note that I had a little formatting problem with Sinon.spy calls here. They should be indented out so hopefully that doesn't read too weird, and the finally, I'm calling process application as I've been doing before. Alright, well, let's add our DB reference here. I'm gonna stub the database as you've seen me do and then I'll pass it in to the review process. So that looks reasonable. I'm making sure that I'm not stubbing things twice. I have it in a before block. Do I think this is gonna work? No, it is not. We've already covered why this isn't working before. Why do you think it's not working? In attempting to wrap the get mission by launch date, which has already been wrapped. Arrgh. What a pain. Well I talked about this before. The simple reason is, as you see here, my DB repository is a global instance. It is a singleton. I am exporting these methods directly on the module itself. Those modules are cached by Node, which makes them a singleton or a single instance. That's not gonna be a good thing if we're gonna try and mock it. Singletons are not friendly to mocking and you want to have individual instances so let's change that around. Our tools are pushing us to do things a better way. Hooray for that! Here I'll just export the DB prototype directly and there. That's all I need to do. So here I'm gonna capitalize our DB repository. That's just a standard that I like to do, and then I'll create a brand new DB down below. That way we can mock the instance, not the global singleton like we were doing before. Okay, we have our DB variable. We're newing it up to a brand new instance, which makes me feel better. We're stubbing that instance. That makes me feel a whole lot better. Let's see if our tests work, and if I run Mocha, and they do! That makes me feel a lot better. Working with the repository as an instance is something that I should've known from the get go. That's what you're supposed to do. Singletons make life hard. Okay, well, this means that it's possible as well to export a stubbed DB instance that is not going to trample on itself. Oh boy! Do I dare do this? Well, why not? Let's give it a shot. Now that I'm working with an instance I can just pass that instance back and I can say helpers.stubDB. Let's see if this works and causes us problems. I'll run Mocha again and hey, look at that. There's no problem. What if I do this over here in our review spec as well? Let's take a look. Again, no problem because I'm working with individual instances. Hmmm. Is this gonna bite me later on? Possibly, but the nice about this is that our repository is small, again, because we're working with Node modules. I have a feeling I'm not gonna need to stub things out very much, so I'm gonna leave it just the way it is for now.
Finishing up Review Process
Well up until now we've listened to our tools and we've gone through some refactoring. I feel pretty good trying to wrap up this review process so let's push ahead and do that. So off camera I've created a brand new model called assignment. An assignment is a thing that happens after the review process goes off and the applicant is accepted and we assign them to a mission. Having a look through here, it's pretty simple stuff. The only method we have is passenger is compatible and what we're trying to do here is make sure that for the selected role that the passenger's weight, age, height and so on is going to match what we need. As I said, working with rocket-shop, well, space, that's pretty tough stuff. You gotta have the right height and weight and so on. I've also created some specs and inside of here you can take a look at those. I've set these things up as I've done before. I don't wanna spent too much time on this because, well, as I mentioned, we've done all these things before and I'd like to push ahead and finish up our review process. If you want to have a look at the assignment code, go ahead, it's in the downloads. Alright, well, I should point out here that in find next mission it's going out to mission control and grabbing the next mission and it's checking with mission control to make sure that a role is available. I also hooked up assignment to make sure that the role is compatible. That's the code I just showed you on my assignment model. Alright, well, if it is compatible, I'm just passing that along and if I run the tests, everything works. That's good news. Alright, well, let's finish up our review process. Our final step here is to save the assignment in the approved application method. So you can see, I'm just passing on true and I wanna change that, so let's go to our DB repository here and I'll create a new method called save assignment and it'll take in the standard args and callback. Then I'll head down to our helpers and go to our stub DB and stub out this method as well. So I'm gonna stub save assignment and for this, I'm going to accept args.assignment, or I'm just gonna create a brand new assignment, so it'll return an empty assignment. It seems simple, right? Well, it's not exactly. If you notice here, our assignment is going to require a passenger and a role and a mission. Oh, man. Things are starting to get a little bit tough. It looks like our centralized stub routine here is not going to work for us, so instead, I'm going to be good about this. I am going to stub the saved assignment right where I'm using it and that is going to be here when I'm testing the review process, only with a valid application. Here I'm just gonna stub save assignment and say that it was saved. That's that. I don't need to have this be centralized. I'm just gonna put it right here. Alright, okay, well inside of approve application I'm just gonna say save assignment. This is the assignment variable, the instance variable that is prepared above in ensure role compatible. However, it looks like our callback is probably gonna change. We're calling next in line and this is going to pass back our brand new assignment. So rather than call this success, let's change the name of this to be assignment, which means that down below so my tests don't fail and break, I'm gonna have to set result.success = true manually. Alright, that looks pretty good, I think. I'm gonna have to change a few tests around. Well, let's do that. It's going to return success, but let's copy and paste this down and say that it returns an assignment as well and then we'll just leave our tests like that. Okay, that looks pretty good and oh boy, we've got an error. Can't yield since it was not yet invoked. Oh, geez. Spelling error. Oh, it had me worried for a second. You have to make sure you say yields not yield, and look at that! It works! That is great! Now having a final look over our test output here, this is making me feel happy. It reads well, it looks good, and the tests are simple to get through. In this module we took at look at ORMs with Node and then we ignored them entirely. We started stubbing things SinonJS and we used the repository pattern to do it. Then we used helpers, we hated helpers, and we listened to our pain and changed a few things around. Finally, we refactored, finished up our review process, and we are now moving on.
A Look at Stripe
In this module we're going to wrap up the complexity as we need to hook up to an external API. That external API is going to be for billing processing of our subscriptions. By the way, what you're looking at here is New Year's Eve in Edinburgh; it was beautiful. So specifically, we are going to take a look at Stripe. I'll give you a tour of Stripe and all the services that it offers as well as a quick tour of the back end, given that we don't want to actually talk to Stripe for each test, we are going to stub and mock the API call and we can use Sinon for that. And then we're going to throw Sinon right out the window and we're going to use Nock, which is going to intercept the HTTP API call and that'll help us test just a little bit better. Then I'll leave you some final thoughts and I will say goodbye. I've been a Stripe user for many years and I have never used a payment system as easy to use and as friendly as this. It handles recurring billing, it handles straight-up credit card transactions. They've also written the check out for you. It helps you with PCI compliance and the documentation, as you see here is amazing. It is so thoroughly and well done and signing up is a breeze, it used to be. But you have to give bank accounts and all kinds of information about your business to these gateway companies and then they would decide whether they would accept you. It usually took weeks. Stripe on the other hand, it's all online. They somehow manage to do it so painlessly, it makes you wonder why other companies don't do this. Here's the Stripe dashboard and this is something you're going to use quite often if you do sign up and start using them. Here you can see at the top left there I've toggled the test switch on, so this is all of our test data. So if we do bounce off of Stripe doing some integration tests later on, we can actually see the test data here. I'm not going to tackle integration testing in this course, but I did want to show you all of the stuff that's up here so when you do run your integration tests, you'll want to be sure that the data that's up here, for instance the plans, mission commander, space tourist, and so on, are represented in your tests as well and indeed they are. So we have four plans here, as you can see. Space tourist, commander, MVAPilot. Okay, well let's get to it. Let's have a look at the API reference. I'll need to work with that API today because I actually am going to call it to make sure that I have the right code written, and if you take a look at this, wow! Look at everything is covered with their API. You can have a look through here as you like. The thing I'm interested in most is subscriptions and on the right side there you can see they have code for Curl, Ruby, Python, PHP, Java, Node and Go. In addition, this documentation is smart enough to know my account details so they actually will input information from my account in here. Specifically, that key right there, that Stripe key that is the actual key from my account, which is pretty neat, and you can see plan. You can see commander right there. They actually seeded it with a real plan. So if I needed to, I could copy and paste this exact code and call it from within Node and have it work. I love Stripe. Alright, well, that's what I'm going to do in the very next segment. Let's give Stripe a call.
Making a Test Call
The Billing Process
Our test code went off pretty well. Now let's wrap it in a little bit of abstraction so we don't have to use the Stripe library directly. First thing I will do is I'm going to create a brand new process and I'm going to call it billing and it'll just be billing.js and then inside of here I'm going to learn from my mistakes of the past and make sure that this is an instance that I can stub later on. We'll drop in an assertion here. We want to be sure that we have a Stripe key. If we don't have a Stripe key, then yes, this should fail. It won't work at all. Here I'll just require the Stripe library directly inside the instance and I'll pass along the Stripe key that was passed in. Next, I'll create three public methods that will allow me to work with the Stripe API as I need to and right now I can only think of three things that I'll want to do. Create a subscription, cancel a subscription, and change a subscription, and notice that I'm using this. I'm making sure that these methods are accessible from the outside. Why? Because I'll want to stub them out. I want to be sure that I can create mocks for them and I can't do that if they're private. Okay, let's take the code that we just wrote and pop it right here inside of create subscription and we want to be sure that we have an email, name, and a plan provided. If we don't have that, well, this won't work. So here what I can do is I can make sure that I have an asserition going once again. This will throw if I don't have that information and that makes perfect sense. I'll then just reformat the stuff down below to use the arguments that are passed in, including the card itself. And then finally, down below I will just simply remove this callback and pass along the callback that was passed to this method. Great. So this is going to send everything off to Stripe. Now to test this out, I can go back to our Stripe.js test page here and instead of using the Stripe library directly, I'm going to call our billing process and I should be able to use the exact same arguments down below. I'll new it up and I'll pass in the Stripe key that I was using before; that's this guy right here, and by the way, I will be changing that. It is a test key so it doesn't really matter if you see it, but I just want tell you I will be changing it. Okay, so I'm going to say billing.createSubscription and it'll pass along this test data including my name this time, and yeah, we'll give this code a run. I do want to be sure that it'll actually work and call off to Stripe so I'm going to do this small little visual integration test, very rudimentary stuff, but I just want to be sure that it's going to work. And giving it a whirl, nope. How many times in my life have I made this error? I forgot to export our billing module, there we go. Alright, well let's try this one more time and success! Yes, I know. This is a bit of a hacky integration test, but I just want to be sure that the code I just wrote actually works. Since I'm not covering integration testing in this course, I thought that, well, I might as well just do this here and now to be sure that the code I wrote actually works. I'm going to talk more about this in a bit. Okie-dokie. So now what I want to do in my review process here, is I actually want to have a method devoted to starting a subscription, so for that, I will need a payment processor and I'm also going to need some payment information on the application itself. This should return a subscription object. That's what we'll do next.
We are upping the complexity of our application and therefore our tests. Now we need to insert some stubbing in here. Let's see if things can go well this time. I have a start subscription method and that is going to work with our billing process and that's going to start things of. That means we need some billing information sent in, so I can send in the entire card here with this card information if I wanted to, or why do I need to do that if I'm not even talking to Stripe? What if I just sent in the number 1? Why would I do that? Well, I'll show you in just a couple seconds. Just remember that I set the value of the card here to the number 1. Now that we're sending billing information in on our test application, I have to be able to do something with it, so here let's assert that we are sending in a subscription processor. I'll just call it billing and that'll be sent in through the args and then I'll create a little variable that I can use down below. Now that we have that, we can go all the way down here to our start subscription method and I can just say billing.createAsubscription and I'll pass it off some information and then I'll just handle the callback. The only thing I need to do now is I just need to format the date that our create subscription method expects to see. There it is. We're going to concatenate the first and the last. We're going to send in an email and for the plan I'll just send in the selected role and the card. Remember that card value is a 1. I'll just send that along, too. Then I will pop this into the Async series call right above the approve application. It's going to be just about the last thing I do. I want to be sure that I can start the subscription before everything is considered a success. Alright, well back in our tests, let's require the new billing module and then down below here I will new it up and I'll pass in a fake key. Now that's critical. If I somehow screw up the stubbing, I don't want to accidentally call Stripe and have a ton of subscriptions created, even if it is, even if it is the test back end, it's kind of a pain in the butt to have, oh no! Look at that! 50 brand new subscriptions. So that is going to be the way that I new up our billing process. Then I'm going to stub it and I'm just going to give the billing instance and say create subscription and I'm simply going to return ID XYZ as our brand new subscription ID. The last thing I need to do is just pass it along here as an argument to our review process. Well, while I'm in here, let's make this look a little bit better. I'm going to drop this down so we can read it and finally, I'm going to take care of that weird indentation that I had for the spies. Okay, well all of that went kind of fast. Let's take a minute and review. I've created a brand new billing process that abstracts the call to the Stripe API and I've put it into a module called billing. Here I'm newing it up and I'm passing in a fake Stripe key and I'm doing that because just in case I stub this thing in the wrong way, I don't want to have a bunch of messy data up on our Stripe test server. Next, I am stubbing the billing instance here. I'm stubbing the create subscription method and I'm having it yield. In other words, when the callback goes off, I'm having null and then an object with ID equal to XYY returned to me. And then finally I'm passing that billing instance to the review process so it can be used. Alright, well I forgot to label this Stripe key and now it actually looks correct. Let's give it a whirl and see if it'll work. And flipping over to our console, and look at that. You know, I have to say, sometimes, sometimes I'm pretty sure it's going to work. Other times I just have no idea and that time it worked on the first shot. Good times! Good times! Okay, let's add a test here and make sure that a subscription is returned when it should be because Async should tack on the result right in our return object. So running our tests, and yes, there it is. Man, that is a good feeling when everything works as expected. Okay, well, the last thing I need to do is let's come up here and instead of using ID of XYZ, let's return our good Stripe response. Remember, I want to work with solid test data because I might be using this later on, so I'll just return helpers.goodstriperesponse and it still works just fine.
Handling Stripe Failure
Well, we've covered the case in setting up a subscription and everything just works. Well, what if Stripe returns a bad result and we want to handle that? So heading over to Stripe, let's take a look at their documentation. In here they have a lot of information about testing and different ways that you can interact with their API and test their web hooks and so on, doing integration tests. Here you can see which card numbers you can use for testing and this works great if you're using Stripe checkout or if you're just bouncing a web page off their API. Down below here, if you want to test specific errors, you sure can. Card declined is, well, one that I can imagine, having it happen, so let's do that. I'm going to go over to Stripe.js here and I'm just going to change this to be the card declined number and that should hopefully trigger a Stripe error. And I'll just change some information here to have a different email address. So let's see what happens. I'm just going to call Node and then Stripe.js and bounce a bad request off of them, and good. As expected, we got a bunch of error information back including a big fat dump here for stack tracing, and I can use that in my test later on. Right now what I need to do is I need to work my helpers a little bit because I need to be able to encapsulate the call arguments that I'm sending off to Stripe. I'm going to need that for stubbing. So what I'm going to do here is I am simply going to copy this information and go and crack open my helpers and I'm going to set up a thing called good Stripe args and this is just going to be a straight up object. It's going to be a test user, firstname.lastname@example.org, and I'll just set this to be commander for the role and the card is going to be 1. Now remember that, card number 1. Then I'm going to have bad Stripe args. What does that mean? Well, for bad Stripe args, I'm going to set the card to 2. Now why did I do this? Well it has to do with the way that SinonJS allows stubbing. The only way I can really explain that is to just show it to you. I'm going to use with args and that means that if I pass in good Stripe args, then it's going to yield a good Stripe response, as you see here. So I'm stubbing the billing instance and when I call create subscription, if I pass those good args, I'm going to expect that I get a good response and I do. That is what with args is all about, because it allows you to change which stub fires. Here, I'm going to say with args, bad Stripe args. When that happens, I want to bad Stripe response to come back. This is going to allow me to tweak my stub, if you will, and while very useful, a lot of red flags and bells and whistles are going off in my head. If you find yourself doing things like this, you know, tread lightly and carefully. Let's see if this is going to cause problems. Well, looking at our test, I've got a lot going on in that before function and it's occurring to me that I should clean things up before I go much farther. Really what I should be doing is I should be doing all that stubbing and creating the billing process as well as the DB and all that other stuff. I should be doing that in the outer most before block. That's going to orchestrate all this stuff for me, right here at the very top level for the review process itself. I don't need it down below for receiving a valid application scenario. Alright, let's see if it works. Ahh! Not this error again. Well, thank goodness this is just a silly thing that I did here. I'm calling stub twice on the billing instance and I'm stubbing the create subscription method twice so of course it's going to fail, but then how did we use with args? Well the short answer is that we create a billing stub without all the rest of this stuff and then we use that billing stub down below. So here, I'm just saying, hey billing stub with args, this is what I want you to do, and then again I'm saying billing stub with args. This is just using the instance, not trying to re-stub it. Okay, well now that I've done that, hopefully our tests will pass. Let's see. And not quite. It looks like I biffed. Yep, there it is. I biffed having that variable available to the entire test suite. There it is. I need that at the very, very top. Same with billing. Now you might be thinking, why is Rob showing me all of this? Didn't you practice this demo? And I did. The thing about it is, I'm trying to show as much of an organic process as I possibly can. Arranging, organizing, and moving things around your test suite is as much a part of testing, as well, writing assertions. So here I wanted to show how important it is to think constantly about your test organization, where variables are declared, and how you're using them. Okay, well, let's see if our test passes now and it does. Very nice. Okay, well that was a bit of a tangent. We were in the middle of trying to test out what happens with failed billing and I was in the process of coming over here and putting this test in and I got a little bit side tracked. Now what I want to do is I want to take this information right here that I declared above and I'm going to drop it in this block down below and I'm going to format it so that everything looks right. And then, right here I'm going to take our bad billing app and I'm going to try and change that card number. I'm going to set it to card = 2. Seems simple enough. So this is going to trip up the billing process. When it sees 2, Sinon is going to return the bad Stripe response. That bad Stripe response will hopefully cause our review process to fail and at least that's the goal. Alright, well skipping ahead a little bit here. Here is our entire test block. Valid application, with failed billing, and I am reusing the valid app from above and I'm resetting the card to be a 2. That should trip everything up hopefully. And then I'm calling the review process as I was doing above, and then I have an assertion down below here that is making sure that it is not successful. Okay, well, let's give a whirl. Flipping back over to the console, running our tests. Hmmm. Nope. False does not equal true. Can you think of why that would be? Well, if we take a look at the test here, everything seems to be put together okay. Hmmm. How about we console.log our bad billing app, just to be sure that it's what we expect? And up here, indeed we have a card number of 2. Can you tell what I did wrong? Let's go back and take a look at the test code. It's right here. The bad response is being returned, but Async won't trip unless it sees an error in the callback. Card was declined. We actually don't need the bad Stripe response. I just need to send it an error, so that's what I'll do here. Let's give our test a whirl and see if this works, and ha-ha, nope. This kind of error is insidious and I'll admit to you I'm doing it a bit on purpose. Take a look at this failure. The test that we want to pass is passing, but in making it pass, we've made our other tests fail. This is ridiculous. Do you know why this is happening? Unfortunately, this kind of thing is all too common with Node. Let's isolate our one failing test above using only. This is the very first test, the one that says things should be successful, and we'll run it again. Card was declined. Now that's strange. Card #2. Why is card #2 being used in this test block up on top? This is supposed to be the successful call with card #1. In fact, I remember very clearly setting it, but it's card #2. I think that Mocha is trying to tell us something. When you see weird errors like this, it usually means you're not doing things right and indeed, the problem is Node's module caching. Every module in Node is a singleton. When you require it, it will be cached in memory, so if you'll use exports.someObject and you set it to be an object that gets exported, then that is treated as a global singleton. Ouch! But that doesn't explain why things are failing in this way. Well the short answer is that the described blocks are read by Mocha first and then the code is executed. Here I am setting the card number outside of the before block. Oh man! Lots of problems going on here. So it actually resets the card number above before any tests are run. Good organization, not getting too tricky, respecting Node's module casting. All of these things go into writing good tests so you don't bang your head on the table. Okay, well let's just prove to ourselves that we have solved the problem. I will comment out the card reset right here and let's rerun the tests, and yep, the test passes, but if I was to rerun all of our tests, our invalid card down below, that test would fail. Arrgh. So how can we fix this? Well, there's two ways. Let's take a look at the first and most obvious one. If I go into the helpers and rewrite our valid application object to be a function, a constructor if you will, that returns a new membership application instance, then it'll work. That will work just fine. That means that I need to go back into my spec file, my review spec, and make sure above here that our bad billing app is set to a new helpers valid application. That'll give me back a brand new instance. And then I'll need to go to the describe block at the top of our test file here and make sure that I am returning a new valid application and then over here to our mission application spec and change the code there. It's not all that much, but if I didn't feel like changing everything, I could use underscore to do this. Let's see how. I could use the clone method. That basically looks at an existing object and clones its properties at that point in time and then you can play with it in a brand new instance. So I'll require underscore above, and then down below here, rather than calling it newHelpers.validApplication, I'll just clone it. In some ways this is clear. In other ways, it's a little hacky and it's up to you. Either way when I rerun the tests, it works. In fact, all our tests work the way we expect because we are now using separate instances, so this is a good example once again of our tests driving us to use tools, driving us to use patterns, pulling our hair out and rethinking what we're doing. If I had to sum up this entire experience, I would say that singletons and caching in Node can equal pain. In fact, twice now we have been bitten by Node's caching of modules and I think our tests are trying to tell us to stop doing that to make sure that we always return instances from our modules, even if they are helpers. Yeah, I think that's good advice.
Mocks with SinonJS
SinonJS is a really helpful library. We've been using spies to make sure that callbacks are called and stubs to jump in and fake out certain bits of functionality in our app. Let's take a look at third thing, which is mocks. Now mocks are a little bit different. They are spies and they are also stubs; however, you can set expectations for a mock, basically saying, I expect this call to be called and you can arrange all kinds of things, so it's kind of a little bit like testing, if you will, making sure that certain things are called when you expect them to be called. To me, this is a little bit much. I don't use mocks, but I know some people find them really useful. So I say, if you want to know when you should use them, use them when you write an assertion to make sure that a method is called. So you saw me do that before. I asserted that the method ensure app valid was being called on a review process. Well, if I was using the mocks here, I could have set that expectation up front and it would've failed our test for us rather than me having to write an explicit assertion. So this kind of thing is up to you. If it fits your testing style, hooray! Let's take a look at how to implement this. Back in our code, as you can see here, I'm stubbing out the create subscription method on our billing object and let's say I want to be sure that it is actually called, so for this, Sinon gives us a mock and for that I just need to say mock the billing object. Next I just need to tell it the expectations I have for our billing instance. So down below here, I'm just going to write out that expectation and I'll say mock.expectsCreateSubscription and that's it. This effectively replaces the stub that I had before. Right now, we are just literally using the mock as a straight-up stub, but if I type in once right here, this is going to create the expectation that that method is going to be called at least once, with this specified argument. If it's not, our test will fail. The nice thing about this is, is if you have a lot of assertions down below that assert that certain methods were called, well, you can replace them all up top and sometimes for people it's a little bit easier to read. Now in case you're wondering, hey wait, are you sure the create subscription is being called once in each case, and it is. Right here in the before block with our valid application where the good card is being called, and then down below, another application where the bad card is being called down there. So since we arrange our mock at the very top, we set our expectations here. The last thing we need to do is we need to tell mock to verify our expectations and we do that using Mocha's after block. This gets called after every test is run. In here, we just say mock.verify. So let's run this and see if it works. And it does. Hooray! Well how do we know that this is actually working and that I didn't goof up? Well let's go back to our test code here and say that we expect the first create subscription we called twice. And if we rerun this, boom! We'll see a failure here, and taking a look at the message, it says twice, but called once. Not the most readable thing, but at least it's fairly clear if you read through the message. So that's the mocking functionality from SinonJS. I don't use it all that often; however, if you have some deep implementation details that you want to be sure are going off the way expect, well the mock object might be the thing for you.
Simplifying Things with Nock
Mocking in any framework is a skill that you have to develop over time and usually that skill involves restraining yourself because, you know, mocking can be destructive at certain points and make you want to smash your head against the wall. In our case, we are mocking the call to Stripe and while that works, sometimes you got to wonder, am I mocking too much? There could be logic and create subscription that we are completely just trampling. When really what we really want to do is we just want to make sure that the API up in Stripe is not being hit and for that we could use a tool like Nock. This thing is bleedingly simple. All it does is mock the HTTP calls from your code to an external API. So take a look at the way you use it down below. You just require Nock and then you instance it, giving it the core URL that you want to use. Then you give it a verb like .get and then some additional part of the URL, and finally, with .reply you specify the return information that you want returned to your code. To me, this is just right. The least amount of interference that have, the better. So let's head back over to the straight API documentation. I'm going to need to find the URL, so for stripe.customers.create, let's hit curl above and this will give us the straight up URL information that we need. Here we can take the core URL, which is https://api.stripe.com/v1 and we can use that with Nock. So now what I need to do is install Nock and I'll save it as a develop dependency, alright? So now that that's in, let's just require it above. Simple enough and now for the best part. I can take out all of this mocking stuff from Sinon. I'm not going to be mocking the billing object anymore. So down here I can leave this stub for our database because I do want to stub that and I will set up Nock. (typing) That's pretty straightforward. I give it the base URL of the API, https://api.stripe/com/v1. We'll just send a post of to /customers and as a reply I have to specify two things. The first is the http code that I expect and that's 200, and we're 200, okay. And then I'm going to tell it the data that I expect to come back and this I can use my helpers and just say I expect back a good Stripe response. It almost seems too easy. Well in case I get anything wrong, I do want to reiterate that I am using a fake Stripe key up here so if for some reason a call goes through, it doesn't matter, it will get rejected. Let's try it out. Wow! That is impressive. It's nice and fast and the only thing that's getting mocked is the http call, which means that the code in my create subscription method is actually getting called. That's great! Okay, well let's copy and paste this down below here for a bad Stripe response and what I'll do down here is I'll just drop this before the same way I was doing above and I'll call this a bad call and it's going to post to the exact same thing, except this time I'm going to return a bad Stripe response. This is the one with the error. It seems like it should work, doesn't it? What do you think? Could it be this easy? Let's give it a whirl. Flipping over to our console and, oh geez. False is not equal to true. Yeah, so it looks like it's still successfully working. Oh, alright, well let's see if we can troubleshoot this. Our bad Stripe response is formatted as the way it should be, but just console.log the result that comes back across this application. I want to be sure that it is indeed returning an error for a subscription and it is. There's our subscription object returned from Async and yeah, your card was declined, yet for some reason success is true. Welcome to Mars. Hmmm. It seems like we are getting into trouble with a very, very simple tool. And as I said before, mocking is easy, sometimes too easy, which makes it hard. Mocking is one of those things that you really have to work on in order to understand when you blow it. And there's an old saying when it comes to mocking that you should never mock something that you don't own. Now I could mock the billing process myself because I coded it, but here I am mocking the http library. I'm intercepting the call and bad things are happening and I have no idea where to even start. And now for a little behind the scenes at Pluralsight, in preparing this demo I actually had to sleuth this little problem here quite thoroughly. It led me to quite a few interesting discoveries, because to me it didn't make any sense. I'm just returning the data that the Stripe API returns. The Stripe library that I have installed here in Node, it should've seen that data and said, ah-hah, an error happened. Well, for some reason it didn't and the only way I was able to figure this out was to actually crack open the Node modules directory and go into the Stripe library and see what it expected. You would think it would be something like an error code being returned. Nope, it was this guy right here. It's looking for an error object on the response from the API. If it sees that then it says okay, an error happened and then it'll stop things. I also tried to return a 402. You would think that that would trip up the library because if you go over and take a look at the Stripe documentation, it'll tell you what the API return codes are all about. In this case, 402 means request failed. Parameters were valid, but the request failed. That is precisely the response that you would think would trip up the library, but no, it's looking for an error object. Oh well. That is something that I had to sleuth and I managed to fix it. But that's the problem with mocking something that you don't own. You have to crawl into someone else's code and figure out how to fail it, which is just never a good idea. So where does this leave us with Nock? Should we use it? Should we not use it? Well, it's a great tool and as you can see, it's quite effective, but when you have to have something fail, make sure you know how it's going to fail and this usually means you're going to have to know a lot about the library that you're trying to manipulate with Nock. So I don't think it's a bad thing. It just requires some investment on your part.
Involving the Database
Many developers I know that use dynamic language frameworks like Ruby or Node or anything with Python, they have just thrown mocking right out the window and they involve the database because as you've seen, sometimes mocking can actually cause more trouble than it's actually worth. So if you're in that boat and you want to be able to persist some information just for your tests, something like nedb might be really useful for you, if you don't want to think about persistence until later on. Nedb is basically like SQLite for document databases. It's incredibly fast. You can use it as an in-memory database if you want, or you can have it write out to files. Installing is as simple as any node module, npm install nedb. Querying with it is equally as simple and it basically tries to use the MongoDB API as closely as possible, so if you're eventually going to graduate to some like MongoDB, nedb is a brilliant first step, especially the in-memory only use, which you can use for tests. Alright, I'll leave it to you to go on up to GitHub and have a look at nedb. For now, I have installed it in my application here using npm install nedb, and I've redone my index.js that's in my DB directory. There really was nothing in here before, but I have now added in a missions and an assignments database and these are just file stores. Basically these things are just JSON documents and you write to them and that's it. So to create these I just create them right at the top here and I can use them down below in my code. As mentioned, if you've ever used MongoDB before, then using this stuff should be really familiar. Up above, I've added a method called clear stores, which drops all the data from missions and assignments using remove. Then for get mission by launch date, down below, I'm using find, using the same kind of selectors that you would use with MongoDB. Really simple stuff and that's the idea. Sometimes it can be just so much simpler to persist this stuff and if I ever needed to transition over to MongoDB as I mentioned before, it's easy to do. So taking a look at how our specs have changed, I'm not using Sinon at all anymore. I've commented it out completely. I'm still using Nock because I don't want to hit the Stripe API. Before everything goes off, I'm clearing all stores of data. Simple enough. And then down below, I'm just letting it all ride. It's persisting the data, my tests still run pretty fast as you're about to see, and it's not causing me any problems. In addition, I have to say I feel much more comfortable knowing that the data is being written and I can get rid of a lot of code just by letting the database go and that's something that's really arguable. I know a lot of people react very strong to that, especially in the .NET world, but just know that in other places, like Rails and sometimes in Node community, people just don't care that much because their tests actually run really fast. Take a look. I'll run Mocha here. I don't have a lot of tests, but it just runs as fast as it has before. One of the things that you got to keep in mind too is that since we're using Node and the modules should stay small and focused, you're not going to have hundreds of tests running at the same time. Ideally, you're going to be testing each module in its own little space. One of the nice things about using nedb is you can just hop on over and take a look at the data that it generates. Here I'm keeping the data inside of my DB directory, right next to my code. You can put it anywhere you like, but here you can see how it's storing the data. It's just JSON and it's got some special system flags in there, identifying records that have been deleted and so on. Here's the one for missions. This kind of thing is helpful if you just want to eyeball the data that's being persisted by your data store. No? Now as I mentioned before, I know this is a fairly controversial topic and I just want to state my opinion and this is my opinion. I know that you're experience is probably different, but let's take a look at a project that I just released. This is MassiveJS and for MassiveJS I had to create a whole suite of tests. Right now I think we're up to about 160. For each test that goes off, I'm running this init routine and this init routine executes a SQL file. That SQL file drops and reloads our entire database including a bunch of data. Here's the SQL file right here. And we're dropping tables, we're reloading tables, we have documents going in. We have all kinds of stuff happening here, and the reason I'm pointing this out is, this is a pretty involved test suite and this is a query that needs to run before each test block is executed. Now a lot of people would look at this and say, man, are you out of your mind? You're going to slow down your testing. Why would you ever do something like this? Well, I kind of have to with Massive because it's a data tool and I have to make sure that it actually works. Take a look this. Look how fast this runs! Right here we have 133 passing tests and it runs in 2 seconds. Now as I mentioned, it's a lot more than that, but we're getting Postgres here and we're dropping the entire database, all the schema, all the files, and reloading. That's a big operation. And it's happening really, really fast. So I guess my whole point is this. Sometimes it's easier just to involve persistence from the get go, and sometimes it's not. That part's up to you, but don't be afraid to involve your tests as you need to. Here as you can see, things are running rather quickly. Yeah, they may run quickly for you, too. In this module we played around with Stripe, got to know the billing processor and how it can help us with recurring billing with our subscriptions. We then created a billing process, a wrapper for the Stripe API and then we stubbed it and mocked with Sinon. And then we got into the weeds and simplified things with Nock, realized how painful that working with singletons and module caching can be in Node when it comes to testing, and finally we took a look at simplifying our lives and just hitting the database and not worrying about mocking at all.
Summary and Parting Thoughts
In this course we went deep into testing with NodeJS and we used some tools to help us out and sometimes back us into a corner. And we're pretty much done with all that. Our client is pretty happy. The Rocket Shop is signing people up and they're going to be sending people to Mars. Okay, not really. I might have made that whole part up, but still I thought it was kind of fun. Wouldn't it be neat to go to Mars? I think so. My goal with this course was to show you how good tests can lead you to want to use some tools to help you with those tests. Those tools can push you to use good patterns, which helps the design of your application, which helps you write better tests. This is a recursive process, and if you stay attentive to the way your code is talking to you, the way your tests are causing you problems, or sometimes a joy to put together. If you pay attention to all that, you end up writing a better application that just feels good. Where we eventually end up is understanding that simplicity always wins, especially in the long run. When you go to pick up your code a year or two from now, like I did with MassiveJS when I iterated it to version 2, you'll look back at the tests you've written and you wonder, what was I thinking? What was I doing? Why did I hack this little thing in here? Oh my goodness! But if you keep it simple, that is always the goal because sometimes other developers are going to come in as well. Sometimes keeping it simple means not leaning on that external library, maybe not taking a dependency on the little test helper, and also, speaking of helpers, not dumping all of the code you want into your helpers' library and just leaving it clear inside of your tests. And that's kind of where we ended up. At the end of the day I didn't do too much outside of Node and Express, Mocha, Sinon, and Nock. In fact, later on I just removed Sinon almost entirely from the equation as I involved the database and all I really needed was Nock to make sure that I didn't hit the http API. To me, this is the key of happiness when it comes to testing with Node and speaking of me, well, that's all I've got for you. I do hope you've enjoyed this course here at Pluralsight and I also hope you enjoyed the videos and little pictures I added in from Scotland, a country I really had a good time travelling in recently. Anyway, thank you so much for watching. If you have any comments or anything, drop me a line on Twitter or send me an email. Thanks again.
Rob Conery co-founded Tekpub and created This Developer's Life. He is an author, speaker, and sometimes a little bit opinionated.
Released14 Apr 2015