This course has been updated! We now recommend you take the Introduction to Node.js, v3 course.
Transcript from the "Asynchronous Tests" Lesson
>> Now let's try to test some async code. This is all cool. You could imagine all the different types of assertions you can do. You could look at the Jest documentation. Let's try some async code. So we'll say another describe here. And we're gonna say let's describe this getNewUser like this.
[00:00:21] And we're gonna test that. Let's go look at the code. What does it do? Yeah, I wanna test that it finds a user. And then I also wanna test if it throws an error if it doesn't find a user. I wanna test both cases. I think a lot of people, when they write tests, they only write tests for the best cases.
[00:00:39] So in this case, they would only write a test to see if it would get a user, and they would ignore everything else. But it's like, what you'd also wanna test when it doesn't get user. Whenever there's a branch in your code, as i an if statement, or a switch, there's a branch, you should test that branch.
[00:00:54] In fact, there's a tool that you can install in Jest, or any other testing program that will literally highlight the code that you did test, and it'll highlight the code that you didn't test. And it'll give you a percentage of how much test coverage you have. And some companies won't even allow you to merge a PRN unless you have a certain amount of test coverage.
[00:01:14] So if you don't know about that, that's a thing. So yeah, there are tools that will statically analyze that for you, and let you know how much code you tested. So test your branches, not just a success pass. So we're gonna test both of them. So let's head over to our test here, and we're gonna write a test to make sure it gets user.
[00:01:34] This is gonna be a async, so I'm gonna prefix this with async, like this. And now what we're going to do is because this is async, and I like to use async await, I don't like promises or callback. There's a couple ways we can do this. I'm gonna do this the way that I like to do it.
[00:01:50] And what I like to do is I like to say expect.assertions. And then I can tell Jest how many assertions I expect to happen. I'm sorry, no, this is get user. That's where the error is. Let's just do the get user. So let's see if this gets a user.
[00:02:10] I'm gonna say user= await getNewUser with a ID of 1, cuz I think that's the only one I have in there. Yeah, ID of 1, Goku. So we'll get the user, and then I'll just say I just expect user toBeTruthy(), like that. So I'll say expect user toBeTruthy().
[00:02:35] Let me get rid of that import. It's not to stop you from doing multiple. I wish it would stop doing that auto importing. There's nothing stopping you from doing multiple expects inside of a test block like this. So we could do that. So I'm gonna say expect user toBeTruthy().
[00:02:59] I'm also gonna say expect(user.id).toBe(1), like that. So we'll run this code, run the tests. And you could see I get a test pass, getNewUser. We did get a user, and it did get an ID of 1, just to make sure if I change this to something else. What, did that work?
[00:03:24] I didn't save it, okay, I was about to say. There we go. I put it here or there. Let's get rid of that. That was a different error. Okay, there we go. That's the proper failure. So received 1, expected 90. So obviously, my test is wrong, but just to show you that works with async.
[00:03:46] But now, if I wanted to test to see if an error was thrown, what I can do is I'm gonna copy this, and I'm gonna test that should. I'm just gonna test no user found. There we go. So before that, I need to say expect.assertions to be 1.
[00:04:12] So I do expect one expect to be called. And the reason I have to do that is because I want to wrap this in a try catch, okay, cuz we talked about error handling, async await. And the best way to do it is have a try catch. And I have to tell Jest that I'm expecting an expect to be called.
[00:04:33] Other than this one, I expect another one to be called. And I have to say that, because if this did not error out, this catch would never happen, and then this test would pass. But that defeats the point of this test. It should error out. That's what I want to see.
[00:04:48] But because it may not error out because the code could be wrong, it will never hit this catch block. And then according to Jest, it's a passed test. But by adding this at the top, I'm saying no, this fails unless at least one expect actually runs. So that means this catch block has to run, and this catch block is only gonna run if this throws an error.
[00:05:08] And if it throws an error, then our tests will pass, so that's how that works. So now we're gonna say expect error toBeTruthy(). We'll just start with that, and then I'll pass in something that's definitely not gonna be there, and we will run this code. And we'll see what happens.
[00:05:26] And obviously, that worked, because this throws an error, and we expected the error toBeTruthy(). So that works. So if we do this, now this is gonna fail, because it doesn't throw an error, right? So now it failed, because hey, we expected one assertion to be called. Well, the catch never triggered, because this never threw an error, because there is a user with the ID of 1.
[00:05:52] So then that means, well, either my code is wrong, or my test is wrong. Well, in this case, my test is wrong. I didn't pass it the right input to trigger an error. So if I want an error, I need to pass in the right input to actually get that error.
[00:06:03] And then we're back to business with all green checkmarks. So this is how you would run tests. Typically, you would see per every file has its own test, if that's how you're doing things. And you could hook this up to a CI, pull request flow, deployment flow, different things like that, run it.
[00:06:21] Some people put them on hooks before they push up to GitHub before they do a commit, different things like that. They're all really good scenarios, and whatever works for your team. But yeah, this is how you would write test. And this is really just the beginning. With Jest, you can add different reporters.
[00:06:35] So right here, this is a reporter. This is something that reports to our terminal, but you could have different things that report to HTML files. So you can actually get a website built for you of all the results, like code coverage, and things like that, or you can change the output of this.
[00:06:48] I was building curriculum for some custom classroom that tracked how many times you would fail. And then it would send you answer some stack overflow. And then it would show the teachers what you were struggling with, so they knew what you were struggling on cuz, they saw you failing it a lot.
[00:07:04] They can get really crazy with the reporters and do some really amazing stuff there. So yeah, this is Jest. And pretty much every other testing framework works very similar to this, since Jest was heavily inspired by those frameworks like Jasmine and Mocha. So yeah, that's how you write test in Node.js.
[00:07:23] And again, this is unit tests. Jest also supports snapshot testing, which is great for UIs. And basically, what that is, it'll take the string representation of your UI, so the HTML, the classes, all those things. And it'll just write that to a file with the exact spaces and everything.
[00:07:41] Everything that's rendered out, it'll write that to a file. And then when you run it again, or write it to a file again, it'll compare the two. If those two strings don't match exactly, then the snapshot failed. That means someone's changed something in its dom that resulted in the HTML looking different.
[00:07:55] That's called a snapshot test. But if you purposely did make a change, then you gotta tell Jest to update the new snapshots, because you actually did want to make a change. So that's a really great way to test UIs. There's other ways you can do it. But those require browsers like taking screenshots and putting it on top of each other, or doing an end-to-end test.
[00:08:13] Those require a browser. But for the most part, you can get away with snapshot testing with Jest. Okay, any questions on testing with Jest? So how do we test code that's internal to a module that we don't have access to? Okay, first of all, you should not be testing code that you did not write.
[00:08:35] You just shouldn't. I'm just gonna say that. There's just no reason why you should test to see if something works. Whoever wrote that should have already written test for it, so the test should live in that repo. If it's an internal module, then you can just trust that the node team already tested it.
[00:08:50] If it's an NPM module that you installed, then they should have tested it. You don't wanna write test. Now, if you're saying that your code consumes those modules, and you want to, I don't know, how do you mock those out or handle them? Well, yeah, you could do something very similar to what I did here.
[00:09:04] But like mocking calls, Jest also supports mocking full blown modules, I think if you make a folder called mocks. And inside this folder, you make another folder. That is the exact name of the module that you wanna mock. For instance, if I wanted to mock lodash, I could do that.
[00:09:28] And then inside of here, I put a file, Jest will read this file, instead of reading the lodash from node module. So you can mock out the entire module if you want to. But again, that's not for you to test it, obviously. That's just so you can get around this code being in your code, and it being in a way.
[00:09:46] For example, if you've ever written it back and had to use something like stripe, and there's all these stripe calls around. Or any API where it's like you don't wanna hit the API during testing, especially if they're charging you for it, and there's latency. You'd probably wanna mock out those API calls to these third party services that you're using during your testing.
[00:10:02] There's no point. You don't need to test to see if stripes API works. That's not your responsibility. They do enough of that. You just need to test everything that interacts with that. You wanna make sure you pass and get the right parameters, and you could do that with a mock function.
[00:10:15] And you wanna test that you handled the output correctly, and you can do that with another mock function. But you don't need to test their code specifically, because they should be doing that.