Express JS #20 - Unit Testing with Jest

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
all right everyone so in this part of our expressjs tutorial I'm going to show you how to set up just and use just to write unit tests and run them for your express application so just is a testing framework for JavaScript it's very popular it's made by Facebook or now known as meta and it's been around for a very long time and you can use just to write tests and run them for really any JavaScript application many people use it to test JS server side apps you can use it also to test react apps or angular apps it's very versatile so let's go ahead and get started so inside my project one thing that I do want to mention is that remember that we are using ES modules so if you recall inside our package.json file you can see that I have the type set to module and all my file extensions are ending with MJS now there's nothing wrong with using ES modules and it is the way to go in the future with developing server side with no. JS however as of right now just they currently do support es modules however it requires experimental Flags so you need to enable an environment variable and while that might work out of the box there are some things that don't necessarily work and after doing some research on stack Overflow and some of the GitHub issues I've came to the conclusion that some of the things that I was trying to get to work just wasn't simply working to say the least so the options that we have are we can either revert our entire project to commonjs so what that means is we remove this type it everything back to commonjs cuz that's what the default system is and then we would have to remove all of our import statements and replace them with require statements so instead of import Express from Express we would have to do const Express equals require Express like this and then we would have to change all of our export statements as well so instead of export cons mock users I would have to do module. export and then pass and mock users like this and obviously that's not something that is possible for many projects because you might have already written so much code and you can't just go through every single file and change all those lines just doesn't really make sense so I figured out a solution where we can actually configure Babel which is a transpiler and we can use Babel to actually take all of our MJS files and kind of like transform them from es modules into commonjs so that we just can actually execute them correctly so we're going to leave our type set to module we're going to leave all of our code the way it is we're not going to change anything at all we're not going to modify the import statements nothing and I'm going to show you how this is going to work now if you're someone that is just trying to learn how to write unit tests and you might already have Babble or typescript set up then just actually has documentation on how to configure just with the right environment if you go to their docs they actually show you right over here if you're using Babel webpack typescript all this stuff so for example if you're using typescript you can use tsj right over here even mentions over here and it's basically just a pre-processor so you can actually use just to test your projects right in the typescript so to get started we need to install a couple of dependencies so I'm going to type npmi hyphen capital D CU I'm going to install these as Dev dependencies so the packages are at Babel SLC core at Babel node at Babel SL preset EnV so that's it for Babel we then need to install just as well and then then let's go ahead and hit enter so while this is happening I'm going to go ahead and go into my project folder and I'm going to go ahead and just set up the Babel RC configuration file so you're going to go ahead and create new file I'm going to call it Babble RC like this and then I'm going to go ahead and pass an object like that and then we need this presets field and this is going to be an array and inside this array we're actually going to pass an array and then inside this inner array we're going to pass a string and we're going to type at Babble for/ preset hyphen EnV and then we're going to pass a second argument or second element inside this inner array so we have this object right over here and this object is going to have this targets property which is going to map to an object and in that object we're going to have node set to current okay so we're just configuring the preset EnV for Babel Okay so so that's pretty much it for the babble stuff now let's go ahead and close that so our packages have finished installing and let's go ahead and type npm init just at latest so we want to configure just and what this will do is set up a just config file for us so I'm going to hit enter and then it's going to ask us some questions so it's going to say would you like to use just when running test so this will just create a test script for us so I'm going to hit yes would you like to use typescript if you're using typescript then press yes but I'm going to press no uh choose the test environment so we are testing a server sided nodejs application so we're going to hit enter for node but if you're testing like a front end application for react and You' want to hit jsd do you want just add coverage reports we'll hit no for that we'll just select it as V8 and then for this one you want to hit yes automatically clear mock calls instances context and result before every test this will ensure that when whenever you run your next test there won't be any leftover uh mock calls or mock data from the previous test and it's actually very important so you can see that it modified the package Json file and then we create this just. config.js file okay so we're not done yet because there's one more thing that we need to do inside our just. config.js file so right over here we need to go to this transform property currently it is commented out but we want to uncomment this part and we want to set this to be an object and now what this transform property will take care of is it will look for those source files that match a regular expression and transform the source code into something that J can actually run which is pretty much plain JavaScript so what I'm going to do inside this object is I'm going to use this regular expression right over here I'm going to copy it and you all can just copy this as well so this will just look for our MJS files inside our directory and what I'm going to do is map this value to babble justest so this is going to be the tool that it's going to use to deal with the transformation and now we actually didn't install Babble justest but when you install just it actually does install Babble justest for you as of right now if you look at your node modules you can see that there should be this Babble just package right over here so that's how we can verify that that package was installed for us okay so we're done with this part and let's just also go into uh right [Music] over make go over here uh module file extensions so we'll just uncomment that and we'll just you can remove the ones that you don't need so I'm not going to be using TSX or jsx or TS uh so I'm just going to remove all that and I'm just going to close this out so we should be done with our setup uh we do need to actually go into our package Json file and for some reason uh when we ran the just configuration it did not override this test script so let's go ahead and do that real quick whoops let's go ahead and do that so all we have to do is just have this test script and then have execute this just binary so this will look inside your node modules inside this bin folder and it'll look for this just binary right over here and it'll just run that command and that's what will actually run your test for you when you are ready so so let's go ahead and write a very very simple test and kind of like explore the just API so what we're going to do inside our source folder we're going to create a new folder and I'm going to name it uncore uncore testscore and this is industry standard and this is where you're going to place all of your tests so you need to create a file and then you want to name the file of your test ideally something that resembles the actual file name so for example if I were testing let's say something inside my users. MJS file inside the routes folder then I would want to name the test file something like users Dot and then the extension here is where you want to name it either dopc or. test and then end it with JS like this so if you were using typescript it would be dope. TS or. test.ts since we're just using JavaScript I'm going to name it dope. jsor . test.js doesn't matter which extension either test or spec that you want to use I personally like to use spec so I'm just going to go ahead and do that and now it's going to go ahead and create this file for me okay let's just explore the just API very quickly and then I'll get to actually testing some of our express route handlers so we have this described function and now you'll notice that right now it isn't going to be imported from anywhere by default just actually so the very first function that you're going to be using that comes from the just API is this describe function right over here now one thing that I do want to mention is that you could actually import this describe function from atj globals like this and then you can just use it accordingly like this the thing is though J typically is configured globally but you can see right now for some reason it is not configured globally so I'll show you very quickly how we can actually configure just to have this describe function and other functions like it test before all before each which are hooks these two are hook functions I'm going to show you how to configure this so that it it's actually Global so we don't have to manually import it in our files so what you need to do is you need to First create a new file inside root directory called JSC config.js and then you're going to set this object and have this type acquisition property which is an object and then you're going to add this include property which is an array and then you're going to want to add just okay now we don't actually have any types installed I believe let me just double check yep we don't have types for just installed is what I meant to say so we need to actually we need to actually install those types in order for us to get it to work so what I'll do and you can kind of see that now it actually recognizes uh just. describe globally but just to be safe make sure you install the at types slj package like that okay even though we're not using typescript you can still use this package to have types set up globally like this okay so now you can see that the whoops right over here we have the just types over here and it should recognize them globally without any issues so going back to our users. spc. JS file I'm going to call this just describe function like this and again no no need to import it and this describe function is used to create test Suites and basically all that means is it allows you to group together your tests into collections so you can better organize and understand understand what's going on so typically you want to give your test Suite a generic name that represents what you're trying to actually test for so for example if I wanted to let's see if I wanted to test for a basic I'm trying to look for something that we can actually test uh let's try let's do this endpoint so let's say for example if I wanted to test this end point where I wanted to get a user by ID then I would just first name the test Suite something like uh get users and then pass in this callback function so inside this callback function is where you will actually create your test closures and those are basically just going to be what's actually going to run your test and you're going to implement the test logic in there so to create a test closure you can use either the it function or the test function they both work the exact same way so I'm just going to use it and then you want to give the test name so I'll just say something like should get user by ID and then you want to pass in a callback function and inside the Callback function of your test closure so the second argument for this it function this is where you're actually going to invoke the function that you want to test and then you want to actually write assertions and verify that certain things occurred okay so here's an next part though we need to actually write a test for some of our functions so for example let's say I wanted to test this router. getet API users ID endpoint so this is getting a user by ID and this is actually one common confusion that many developers begin to have when it comes to runting tests they look at this and they're like well how do I actually test this whole endpoint because there's so many things that are going on do I need to actually make an HTTP post request or get request to the actual endpoint that I'm trying to test so first of all when it comes to unit testing you want to actually test only a single piece of your code look for individual functions that might be calling other functions maybe they are calling a database maybe they are calling an external API whatever it is you want to test that single function and you want to make sure that the function does what it's supposed to do so ideally you want to verify that the function is hitting the right IFL statements the conditions correctly and then based on those conditions that are being executed you want to make sure that it is returning the correct response so to give you a very simple example let's take a look at uh this function of here okay so you can see that this function is our request Handler and it first takes this find user index property from request and this comes from our resolve index by user ID middleware so don't worry so much about that right now but we grab this property from the request object and then we try to reference mock user array and Index this user so we can grab the user with its index so if the user is not found in this mock users array it's going to go ahead and return a response and send a status of 404 if the user is in fact found then it's going to go ahead and call response. send find user so what you need to understand is this function is pretty simple it doesn't really do too much stuff but the point is is that we want to make sure that we understand what are the possible comes of our function in this situation there are two possible outcomes only only two either the user is not found and we return a status of 404 or the user is found and then we just send back the user that was found there's only two outcomes sometimes your functions might have many different outcomes it might output many different types of results based on the condition and there are some general principles when it comes to writing your code in a way that makes it easily testable because writing tests also depends on the code quality as well if you have a bunch of spaghetti code you're making it really difficult to actually write tests okay the purpose is not to actually write the most complex code but to separate concerns and make sure that you have each function doing the main responsibility and anything else you turn it into a dependency function is what we like to call it and that basically just means that you're invoking another function and letting that function take care of the work and then once that function is done it'll return back the result and then you can proceed so we'll start off with a very easy example we'll use this call function right over here but now here's the other problem though okay when we actually want to write tests we want to be able to import the function or class or whatever it is that we're trying to test into our test file so how do we import this Anonymous function there's no way so what we need to do is we need to copy this function function and we need to move it into uh we we need to turn it into a named function so what I'll do is I'm going I'm going to actually create a new folder called handlers and then I'm going to create a new file and I'm going to call this users. MJS and then what I'm going to do is I'm going to paste this function right here and then right before the parentheses I'm going to do export const and since this request handler was trying to get the user by ID I'm going to go ahead and call the function get user by ID Handler and let's make sure that we are importing the correct value so we need mock users so I'm going to import that up here and I think that's all we need so let's go ahead and copy this function and we want to make sure that we pass it as an argument to uh our router. getet method call so at the end like this and then we're going to make sure we import that up top over there as well and then now we have our function being passed as an argument it's still going to work the same exact way the only difference now is that we can actually import it wherever we want so I'm going to go into my users dope. JS and what I'm going to do is first I'm going to import that function so get user by ID Handler and so this is going to be our system under test or function under test and now we want to actually call this function okay but we need to also remember that this function does take in two arguments it takes in a request and a response so this is where you start to learn how to actually create fake data or mocks so that way you can use them for your test subjects in this case are get user by ID Handler function so so when it comes to creating mocks there are ways to be smart with it so what we'll do is we'll create two simple objects let's call this mock request and then mock response and remember this the request and response object resembles the actual request type in expressjs okay so if you were to actually look at let me go over to here because we're using JavaScript okay so if I were to actually rightclick the type definition of this request you can see that we have this interface and you can see that there are a lot of properties on the request object itself okay you can see that we have uh host cookies method prams these were similar methods or properties that we looked at earlier signed cookies as well now you're probably wondering well when I create my mock object like my mock my mock request do I need to also include all of those fields and the answer to that question is actually no so what you can do is you can only Define the fields that you need okay and again this works because we're using JavaScript if you're using typescript then you can use uh casting for example you can type annotate the request and force it to be an actual request type even though it is missing all of its Fields but since we're using JavaScript we can do literally whatever we want so for our mock request we want to include the necessary properties that our code is actually referencing on the request object so this is where you need to actually look at your code look at the function that's being tested and you need to see okay what is actually being referenced on the request object so I can see right over here one property is is being destructured from the request object so we need to make sure that this whatever this fine user index is we know that it's a number but whatever it is we need to make sure we Define that in the mock request object um and it seems like that's the only property that we are referencing so I know that this is a number because in case if you didn't watch the early parts of the tutorial this resolve index by user ID middleware function what it does is it will go ahead and grab the ID from the parameter and it'll take care of parsing it to an actual integer and then it'll attach it to the request object so what I'll do is inside my mock request I'm going to go ahead and Define find or what was it called again uh find user index yep find user index and then you can give it literally any number you want you can give it one two three 100 literally anything you want but here's the thing though we want to give it an actual meaningful value that is going to correlate with our code so if you look at the rest of your code you can see that fine user index is being used to retrieve an element from this mock users array and this is our mock users array right over here and if I were so for example if I were to just give like just some bizarre number like 100 it's pretty obvious that that number would not actually give us back anything inside mock users because there is no element at subscript 100 that would give us back a user okay so let's just do something simple for now let's just do one so that way I know that it'll give me back the user Jack okay all right so we're done with the mock request object now let's go into the mock response so this is going to be our response object and now we have again we have to do the same thing we have to look at our code and look at what response uh is and we have to also see what is being referenced on response so right over here I can see that response is being referenced right over here and the send status this is a method on the response object is being called okay so I know that send status is a method so that means that inside the mock response object we should set this send status property and have it mapped to function so you might think that it would look something like this right well no so it is going to be a function but it's not going to be a regular function it's going to be a mock function so what we can do in just is we can use this just namespace and I can reference just. FN like this and what this will do is it'll create a mock function for me so this is where we actually want to call a mocked function of send status on the response object and not the actual send status method itself okay so we use just FN to apply this mock function to send status and then we'll do the same thing for the send method as well so let's do send and then this will be just. FN okay all right so now what we want to do is we want to actually take both of our objects mock request and mock response and pass them as arguments to get user byid Handler so let's do that so mock request and mock response okay so now if I run my test right now the test actually should just pass because we're not actually testing for anything so in our terminal we'll run npm run test and this will execute that justest binary and you can see that our test runs and it passes and just very quickly I want to show you what would happen if I were to go over here and just kind of like remove this part entirely and you'll see that it's going to go ahead and complain about all this stuff so that's the reason why we need this transform part okay and again we can also run our application completely separate from our tests like this and it still would work just fine okay so hopefully the setup with Babel and everything else wasn't too complex and we can still proceed to use es modules and write tests okay you can see it's currently running without any issues well awesome so let's go ahead and go back to our test and let's actually write our first assertion so again let's look at our code again okay and what we can see is there's two outputs there or two things that could happen either we send a status of 44 or we send the user back so this is where we need to understand okay well what should happen inside this test is whatever it is that we want it to happen Okay so we have to figure out what it is that we're trying to test for so in my situation let's say I want to test to ensure that a user was actually sent back okay and so we already set up our mock request to have F user index to be a value of one so when the function is called it's going to use one as the index and search for that mock users so let's actually write our first assertion so again we need to understand what it is that we're trying to test in my situation right now I want to test to make sure that the user was actually found and sent back with the response if I look at the code okay how can I verify that the user was sent back well what I can do is I can write an assertion that verifies that this response. send method was called and it was passed it passed this find user object as an argument and I can also write another assertion to verify that send status was not called because send status would only be called if fine user is undefined and since we are setting fine user index to be one it's going to use that as the index to reference the user at that position and mock users is just this constant array that we have over here so we can just reuse this data because it's not like it's coming from a database or some external API so we can use this as mock data so let's go back into our test and here's what we'll do we're going to use this expect function like this and we use the expect function to actually test a value so for example I want to test that mock response. send like this I want to test this specific function and then at the end of expect at the end of the uh parenthesis we can use these just matchers and the one that I want to use is to have been called so what this means is we are expecting mock response. send we're expecting this function to have been called okay so that asserts that the function was called when we called this get user by ID Handler so let's actually test this out and see what happens so let's run npm run test okay and you see that it passes okay so again this it takes some time to really you know get used to some of these matchers and knowing how to use uh expect but as long as you just keep practicing it's going to come in it's going to come in handy and you're going to develop an intuition behind it so you can see that we definitely wrote an assertion on this send function and we asserted that this function was called and you'll notice that right now if I were to try to negate this matcher so if I actually use doot do to have been called that basically just asserts the opposite so it's going to assert that mock response. send was not called so if I run the test it should fail because the function itself was actually called okay hopefully that makes sense and it's it's sometimes good to do that because it it helps you assure that you're not having your test pass falsely if that makes sense so you don't have any false positive cuz sometimes your tests may pass when they actually shouldn't so let's actually be more specific with our test so what I'll do is I'll duplicate this line and I'm going to use the matcher to have whoops to have been called with and this is what I want to use whenever I want to verify that a function was called with specific parameters so the send function is called with only one argument and this argument is a user itself now again since we are using a mock array I can very easily just reference the array at the subscript of whatever fine user index is which is one so I can just go ahead and import this mock users right over here so let's import that and I gets import up top here and then I'm just going to access it like this okay or if you wanted to you just literally copy this whole thing thing and paste it in like this either one will work the same way cuz they're both going to be the same object and the constant are the same let's go ahead and run our test now and you can see that it still passes and you'll notice that if I tried to let's say if I tried to pass in this direct object and let's just say if I tried to change some property values right over here let's change it let's change the username you're going to see that the test actually fails because the value that was actually passed as an argument to the send function is not the same as what we were asserting it to be so you can see that the expected value we expected uh username of Jack without the K so jaac but then the actual received value was username of Jack so again this is good to make sure that your tests are actually passing correctly okay so let's run the test again and it should just pass just like that all right so hopefully this makes sense and there's also one more assertion that I want to show you so we can also check to see the amount of times this send function was called so I can do two have been called times and I can specify the expected amount so if I want to verify that this send function was called once then I can just pass one and it should only be called once especially in Express because if you try to call it twice that will actually throw an error so let's run the test again and of course if I try to pass in like two it'll throw an error or the test will fail you can see over here the actual received number of calls was one okay so hopefully that makes sense and one more thing as well that I forgot to mention is that with this two have been called with matcher you can actually pass in the number of arguments that the function is called with so we only have one over here but if you had a function that was called with like multiple arguments you can pass in the exact amount okay so let's go ahead and move on let's write another test to handle the second scenario of our function so the other scenario would be where fine user is not found and it sends back a status code of 404 so I'm going to write another test using the it function and I'm going to go ahead and say it should uh call send status with 44 when user not found okay you want to keep your test names short and simple and we're going to go ahead and write our test so now here's the thing though okay if I were to call get user by ID Handler again and then if I were to pass in the same mock objects it's going to actually give us this same response that we have up over here but the problem is now we want actually get it so that we can set up our code to ensure that the send status function is called instead of the send function okay if I were to just leave mock request alone you can see that it's still using the same value for fine user index and in the code it'll actually look for a user and return that user so it would never hit this part at all line six so we need to actually modify the mock request object so what we need to do is there there are a couple ways that you can do this if you prefer to not modify the object directly like this and set it to a value like this you can always just create like a copy of it so you can do something like const and then you can do mock request or let's just do copy mock request equals and then you can do something like find user index is equal to 100 and then of course you can also destructure mock request because there might be some properties that you want to uh copy over to the copy of the mock request so you can do something like this and then pass in copy mock request to here for mock response we don't really have anything that we need to change because we still have the same functions so it doesn't really matter so we can leave that alone okay so we're going to call get user by ID Handler with a new request object but we're going to set the find user index property to a value that I know that will not give us back a user in this case we'll set it to 100 okay so when we call this function we now want to write some assertions so what I can do is I can write an assertion to verify that this send status function was called so let's do expect mock response send status to have been called and let's run the test and you can see our second test passes without any issues let's write a few more assertions so let's do expect mock response send status to have been called with 404 let's run the test again passes and I can just throw in a value that I know is actually not going to pass you can see that it fails so that's good because it shouldn't be sending a 400 it should be sending a 404 and I can also do to let's do to have been called times let's do one let's run the test again and let's also write an assertion where we verify that mock response. send was not called so expect mock response send not to have been called let's run the test wonderful so all of our assertions are good now one more thing that I do want to do though is if you go inside your just config file and if you look for Clear mock you want to make sure this is set to True earlier when we were configuring just we had pressed yes when it asked us if we wanted to configure um our test to clear all mock calls instances and context and result results before every test because what happens is if this is false the next test will actually carry over data from the previous test so if I actually try to run my test now you're going to see now our second test actually fails and it's actually failing because right over here on line 32 where I have this assertion expect mock response. send not to have been called you can see that it's failing because it says receive number of calls one so it in fact was called but if you think about it your code itself says otherwise because I know for a fact that in my code response at sense should not be called because we are returning response. send status so this code doesn't get executed when F user is uh not defined Okay the reason why it's failing this test right over here and it's treating it as if s was actually called is because in the previous test the send function was called and it was called one time okay okay so since it was called one time and we didn't clear the mock it carried over to the next test right over here inside this second test over here so there's several ways they can do this the first thing that you can do is obviously what we already had was having clear MOX set true instead of false the other thing that you can do is you can use this before each hook and this takes in the Callback function and basically you can execute some logic that happens before each one of your tests so you can initialize a variable you can connect to a database whatever it is that you want so you can actually use this just. clear all mocks function call and now if I run my test it passes okay so this is manually clearing all the mocks before each test sometimes you might not want to clear all the mocks because you might want to depend on previous function calls from the previous test so you might want to to turn off clear mocks and set it to false sometimes you just want to manually clear the mock so you can use this before each it's up to you but I just wanted to mention that as well so I'm just going to remove this before each and I'm going to set clar MOX back to true so hopefully these two tests that we wrote makes sense now I have one more example where we can write a unit test for another one of our end points but this one's this one's going to be a little bit longer so I just wanted to warn you all but I'll try my best to keep it as concise as possible and and I want to do this one because there's a lot of stuff going on inside this function and I think it would be really great for you all to learn how to test different scenarios okay so what I'm going to do is for my post endpoint for API users this request Handler creates the user and saves it to a database so you're going to learn how to mock functions that come from modules you're going to learn how to mock uh classes es6 classes you're going to learn how to mock the database call so that way we don't actually hit the actual database because when it comes to unit tests you don't want to actually invoke your database or call an external API so you're going to learn how to do a lot of stuff with this next test so we need to make sure we get this function um out of this uh out of this format we don't want it to be an anonymous function so I'm going to just copy that and I'm going to go inside uh let's see not here but inside my handlers file or the users MJS file inside handlers and I'm going to go ahead and paste this here and I'll name this function create user Handler like this and uh before I import all of the necessary functions into this file let me just take this and let me just paste it here and import it up top over here okay and now let's go ahead and go back to our users. MJS file inside the handlist folder and we need to make sure we import all of the necessary functions that we imported that were in the other file so so validation result comes from Express validator that's a validation library that can validate post requests um request bodies query parameters all that kind of stuff we need to also import match data that's also a function from Express validator hash password is a function that we wrote ourselves let's import that right over here uh user is the model so this is what actually allows us to interact with our database but we're not actually going to call the database we're going to mock out this as well um and I think that should be it okay so now let's go ahead and go into our users. spc. JS file and then now I'm going to create a new test Suite so outside of this describe get users I'm going to go ahead and create another describe and I'm going to call this create users so you can think of these individual it functions these individual tests as your scenarios okay so inside the create users test Suite I'm going to go ahead and create a simple test and I'll just say it should let's see let's go back into our function okay so this is a pretty big function does a lot so I want to take this step by step so that way you all don't get confused first let me just remove these console logs because we're not going to need them whoops so we want to take this step by step because there are a lot of things that are going on so let's hand let's take a look at our code and understand what's going on so when we call this create user Handler um when Express causes function to handle the uh create user endpoint it's going to go ahead and call validation result so when you're looking at the code the important part is not to really worry about what your what these functions these external functions cuz we don't know what these functions let's just pretend we don't know what it does you don't have to care about what this function does you have to care about what it returns because we're going to mock the function anyways we're going to mock the implementation we're going to mock the return value of the function so in the end it doesn't really matter okay what I care about is what it returns so result you might not know what it is you might think it's an object it's a number it's a string you might not know but you can very easily tell if you just look at the rest of the code and look at where result is being reference so you can see right over here that result has this is empty method that's being called on it and I immediately know that this is an object okay uh there's also this array method on result as well so what I can do is I can go ahead and actually mock out validation result because this is known as a dependency it's a function that that can perform some kind of side effect and we don't want to actually have any side effects in our unit test we want to make sure that we mock them all out and we only want to test the function itself we only care about the possible paths are function that we're trying to test and take so again you can see right over here this is our first if condition if result uh dot is empty if it is uh let's say if this results to false and then we negate that value so if it is not empty then we're going to return this response status of 400 and then send back whatever this value is again I know it's an array because we wrote this out ourselves but we're just pretending that we don't know okay so this is the first thing that it could do the second thing that could happen is is right down over here so you can see that we have this new user. saave being called and then it sends back a status of 2011 and it sends the user okay so this is the second scenario the third scenario is that an error could be thrown inside this Tri block so then the save method could have been could have thrown an error and then we catch it down over here and then we send back a status of 400 okay now there actually should be another scenario scario where the hashing password uh failed and we actually should have added this inside the try catch but we're just going to leave it the way it is right now okay so hopefully all of this makes sense so now if I were to go back up top over here again the validation result is one dependency okay so what we want to do right now is we want to test our function to handle this first scenario first because instead of just having to do everything all at once we're going to take it step by step okay so what I'm going to do is I'm going to write a test that is going to mock out this validation result function call we're going to have it return uh literally whatever object we want but that object specifically needs to have this is empty method as well as this result uh as well as this array method on the result object because they are both going to be called okay so we need to make sure that whatever is empty is going to return it returns false for this case because I can see see that we negate the value of result. is empty so for example if there are no errors that's what this isempty method means if there are no errors you can see right of here it returns true if there are no errors so if this is true the negation of this would uh not execute this line and it would just go down over here which means that there are no errors however is however if is empty is false then result that is empty would return false which means that we negate that which means it would return this part of here so for our first scenario we want is empty to return false just so that we can Target this line over here so what I'm going to do inside my users. spc. Js file is this so I actually already have some mock data defined up there um just to keep things simple I'm actually just going to Define it inside test Suite so const mock request and then const mock response and then it'll be within the close osure of this callback function so we don't have to really worry about the ones defined up there so what I'll do is for the test scenario I will say it should return a status of 400 when there are errors and we'll pass on our callback function okay so now we need to go ahead and set up our mocks our our mock request and mock response to actually get this to work work so again we have to look at our request object and we want to see Within These two lines where is our request object being referenced you can see that it's being passed as an argument to validation result however it's not going to really matter because we're going to mock validation result so we actually aren't really going to need to do anything with the mock request for this specific scenario okay so let's look at response I can see that response is referencing this status method right over here so right over here we're going to go ahead and do status just. FN and you know what I actually could just reuse mock response up top over here because we're going to be using some of these similar methods anyways so you know what what I'll do is I'm just going to remove this and I'll just reuse the mock response up here and it shouldn't matter because we are clearing all of the mocks uh anyways before every test so we won't have any leftover data from the previous test okay so let's go ahead and implement the status method and this is also going to be a mock function so we'll use just. FN okay now one more thing that I do want to point out is that after we call the status method we actually are calling the send method as well because when you call this status method it actually Returns the instance of the response itself and you can actually call the status method on it as many times you want because it just Returns the response instance itself so in order for us to actually detect calls on the send function inside uh status over here inside just. FN for status we actually want to pass a callback function and we want to actually return mock response so basically returning itself so that way by returning itself mock response would then have access to all these other methods as well and if we don't do this we wouldn't be able to actually test to see if send was called after status and I'll show you that when we actually write the test okay so we're not done yet we're done with setting up our mock request and our mock response we need to now mock this validation result function and you're probably wondering well how do I do that this is a function that comes from a thirdparty module Express validator well I'll show you so you can mock modules and what that basically means is you can override the implementation and the functionality that that module that module's API provides so for example inside the express validator module we have a bunch of functions we have a bunch of different things that we can import that we can use in our application however we want now for our case we only are using validation result currently so we want to mock validation result specifically so all the way up top over here what I can do is I can reference J.M and then I want to specify the name of the module so I'm going to go ahead and say Express validator like this so This Will Mock that mpm module you can also mock your own modules that you create as well so for example uh later on when we need to mock uh the hash password function we're also going to mock this helpers module as well okay and that's our local module so as a second argument after we pass uh Express validator for the first one the second argument whoops the second argument is going to be a factory so this is where you're going to return an object cuz if you think about it your modules are kind of like objects and those objects have um the named exports okay if it assuming it does so what we want to do is we want to return this object like this so notice how I have the parenthesis wrapped around the curly brace so that just allows me to do it shorthand instead of having to do something like this okay so what I'll do is inside the object I want to mock the validation result function so I'm going to type validation result like that and I'm going to do just. FN like that so now I have successfully mocked the validation result function for the express validator module and now this mock function we want it to actually return something so that way we can actually uh have this result value be something that is defined with the correct is empty and array methods so we can do that inside our just FN function we can pass in a callback that also returns an object as well and I can go ahead and Define the isempty method so is empty is going to be a mock function as well but this mock function remember we're going to have it return uh false okay so passing that call back return false like this and then we also want to implement this array method on the return value of validation result so this is a method and uh it does return an array so we will have it return an array like this and then what you can do is you can add some random object with random Fields so you can just say message invalid username just like an example you can literally put whatever you want like invalid field whatever it is is okay because we can always actually modify uh we can always override the return value of validation result and I'll show you how to do that as well okay so I think we're good to go let's actually go ahead and test everything out okay so what I'm going to do is I'm going to go ahead and call create user Handler so let's go ahead and import that up top over here oh whoops I can just import that from right over here wonderful cuz we're it's from it's coming from the same file okay so let's go ahead and call create user Handler let's pass in the mock request and notice how we're referencing this local mock request cuz this is the closure we're inside this closure over here and then we're going to reference the mock response which is all the way up there so we are reusing that same mock response object from earlier okay and remember that this function is asynchronous so we should also await the call and we need to make sure we add the the Asing keyword in front of this callback function for the it function okay all right so let's just make sure everything is okay so if I run npm run test the test does pass but we aren't writing any assertions so that's okay and you'll notice how now if I actually try to kind of like remove all this all the mocks that we just did you'll start to notice that a lot of stuff uh starts to error out and you can see that um it's actually trying to go to line seems like like line 8 over here inside the helpers MJS so it's actually without mocking anything it actually tries to call the actual uh Express validator uh Library so it's actually calling the functions okay and then it's going to try to execute everything the way it is but you can see over here it tries to throw this error right over here because we don't have a value for password but we'll get to that because right now our request body has uh nothing okay and we also aren't mocking match data yet so don't worry step by step just wanted to show you what would happen if we removed the mock stuff okay so let's add that back in and you can see that everything will work the same way all right so now let's actually write an assertion so the first thing that I want to do is this so when it comes to testing there are different ways that you can approach testing there is there's implementation testing and that's where you're actually testing the actual implementation so you're trying to verify that certain functions are called certain functions are called with certain parameters that's known as implementation testing there's also behavioral testing where you just give it some input you don't care what it does as long as it gives you an output so there's different ways on how you can test this with backend you can't really go wrong with either one usually on the front end like if you're testing apps and react you would want to use behavioral driven testing because you want to test based on output you don't care about what actually happens underneath the hood with all of the state and all of the uh you know form fields and whatever is being called to the API stuff like that so I just wanted to point that out okay but since we're just getting started I'm just going to show you very basic how to write assertions so what I want to do is I want to verify that validation result was called with the request object okay so the question is how do I actually write the assertion for validation result because in my whole test file I don't have any access to validation result well I'll show you so the first thing that we can do and I'll import this over here on line one is I'm going to import the entire Express validated library but I'm going to import it as like a default import like this so import validator from Express validator like this and then now down over here on our test after I call the create user Handler function I can write an assertion and I can reference validator do validation result to have been called times 1 and let's run our test and you can see that it passes and if I were to set this to zero to see if it actually fails and it should fail you can see that it was actually called once so that verifies that it was actually called and if I were to actually remove uh if I were to actually just kind of like remove this whole thing or over here here you can see that our test begins to fail and it's not recognizing validation result as a function if I remove this whole just. Mock and if I try to run the test again you're going to see that other stuff starts to fail as well okay so hopefully this helps you understand the importance of mocking out your modules and hopefully this helps understand how to how to mock out uh these functions that come from the Express validator package or just any package in general General okay let's go ahead and continue so I'll do validator do validation results so I'm going to write an assertion to say that to have been called with mock request because if you look right over here we are passing whatever request object this is to validation result so we'll write the assertion there it passes and let me just change this to two have been called instead of two have been called times one okay that's a lot more better cuz this basically implies it's going to be called once or two have been called at least once okay so now uh we want to also write an assertion that response. status was called with a status code of 400 so let's do that so expect response Dot and I think it was status and it's mock response to have been called with a 400 mock response okay let's run our test okay it passes good and let's go ahead and write an assertion to verify that the send method was was called so we can do expect mock response and this will be mock response. send not mock response. status. send because remember that when we call the send method we're still calling it on the instance of the response method so we would have to reference mock response so to have been called with and then we want to verify that it was called with this array and that's whatever we had mocked over here so I'm just going to take that copy and paste it down over here let's run the test again and it passes and again I can go ahead and just change everything up and make sure that our test is passing correctly and you can see it throws an error when I pass in the invalid expected argument okay so that's pretty good let's go ahead and move on now CU we've covered this whole part over here so now we have to cover the next few lines over here everything up until down over here so we are calling this matched data function but we aren't mocking it okay so it's going to actually call the actual match data function which comes from Express validator so we want to mock it because what I want to do is I want to mock this function to return uh some object which is going to be the actual user data so again to give you context uh what this function does is it takes the request body and it gets you it gives you the validation result okay so if there are no errors then what happens over here with match data is match data will extract all of the valid fields in the request body and store inside this data object so all we really need to do is just mock match data to return a user a valid user object so let's do this okay so going back up over here I'm going to do match data just FN and we're going to have it return an object and keep in mind that whatever the return value of matched data is is actually just going to be an object so there's no methods on it that we would need to worry about because we're not referencing anything except for just the password field so let's just do uh let's see username let's do test password uh password and then I think the other field was uh I think it was display name if I remember correctly let me check if I go inside my validation schema so we have username display name and password yep so display name just do test name now some of you might be asking well what's the point of doing all this mocking because doesn't that kind of defeat the whole purpose of testing well no not for unit testing because remember our goal is not to actually test to make sure that you know Express validator Works our goal is to test create user handle Works our own function this is a unit test okay when you want to actually test the entire application that is where you want to actually set up an integration test or an endtoend test which you will learn about it later okay so hopefully that kind of clarifies the confusion between uh mocking and actually testing the entire thing without mocking it okay all right so now that we've mocked match data this function to return a mocked result so we can safely uh run the rest of our code and then we want to make sure that this match data function is going to be called but we also want to make sure that we don't hit this if condition and make this true so we need to actually make is empty return true this time which means that there are no errors but the thing is though I mocked it up top over here to return false so what if I changed it to true but then that would break my first test over here so we can't do that so I'll show you what we can do to actually change the return value for our specific tests okay because we want this is empty function to return uh true for whenever we call it validation result so that way this if condition results to false and then it'll go down over here and call match data okay so let me just make sure that we have our match data function mocked properly okay this is good let's go down over here and let's rate our next test and I'm going to go ahead and call this test uh let's see so I guess the overall thing that we are trying to test for is making sure that the response. status is sending back a 2011 and it's sending back the user that was created so essentially we're we're testing for success for creating the user so I'll just say it should return status of to1 and the user create okay and let me actually go up here and let me fix this should return status of 400 okay so let's add the Asing keyword in front of our callback function for this test and now we're going to go ahead and call create user Handler so after we call our function we want to actually mock out the is empty method to return true so that way it goes to the match data function call so inside our test here's what we're going to do we're going to use just. spyon and I'm going to go and reference um this import up here validator so that's the entire Express validator package and then we're going to spy on uh this function right over here it's validation result like this okay and then what I can do is I can use this mock implementation once and then in here this is where I can actually mock the actual implementation of the function and I can have it return an object so the same result object but this time that object will have this is empty function so this is also going to be a mock function and it will return true like that okay okay so hopefully that makes sense so this is how I can override the mocked value of validation result into something else so now what will happen is create user Handler will be called it's going to go ahead and call validation result and then result that is empty will be called and we can see that's going to result to true and then negating that will cause this whole of condition to be false and then it'll go down over here so what I can do is I can write an assertion to verify that matched data is called so let's do expect so remember matched data is a function that comes from the Express validator package so I'm going to reference validator do matched data to have been called and let's save and let's run our test and let's make sure that it passes okay so it actually is um let's see okay yeah don't worry about this so it is actually supposed to pass but the reason why it's failing is because right now it's actually trying to uh call the next few functions it's trying to call Hash password and then it's also trying to call the database by Saving this new user to the database so don't worry uh it is working it's just that um we're it's trying to connect to the database and you can see that it actually timed out because we don't actually have a database connection so don't worry we actually did the right thing we just need to mock the rest of our function okay so we know that match data is being called and you know what let me just comment all this out and run the test again because I just want to show you all that it passes gr so now let's go ahead and mock hash password because again we don't want to actually call these functions because remember these functions these dependencies produce side effects okay so we're going to go up top over here and now I want to mock this hash password function that comes from my helpers module so what I'm going to do is I'm going to call just. Mock and I want to pass the path to that module so we're in the test folder so we need to go out one folder into utils helpers MJS like this okay and what I want to do is I want to have hash password uh um I want to I want to mock the implementation so let me pass in the factory and return an object and just overwrite hash password and so this will be a mock function but I I actually want to have it return some custom logic and all I want to make it do is takeing an argument so I'll just do password so inside this callback function I can pass in this argument password and all I'm going to do is just concatenate um hashed with password like this I'm just going to literally prefix the password with hash so that way at least does something okay all right so we just mocked hash password or hash password so now let's go ahead and import up top over here we're going to import helpers from utils helpers okay and this would allow me to reference helpers like an object and I should be able to reference hash password or maybe I need to do import as helpers yeah there we go okay I think I may had need to do that as well for validator I'm not too sure but let me just run everything and make sure it's good let me go over here and just come this out real quick okay there we go perfect okay just wanted to make sure that didn't break okay so now we're going to go down over here and we have wrote an assertion for the Matched data function okay to have been called uh let me also change this to have been called with mock request to be more specific and then we're going to go ahead and write an assertion for hash password so what I'm going to do is I'm going to expect helpers Doh password to have been called and I want to verify that it was called with data. password so data. password is going to be whatever matched data return so up top over here matched data returned password so I'm going to say to have been called with password like that you know what for the rest of this test let me just comment out all this stuff over here because I do want to actually see our assertions passing okay good so to have been called with password and then I also want to make sure that hash password so helpers hash password returns with the correct value so to return to have returned returned with hashed password and I know I'm just hardcoding these values but ideally for you you would probably not want to hardcode these values so that way you can make it more Dynamic but I'm just doing this for testing purposes okay just to show you an example so if I run the test you can see that it will in fact return hash password and that's just our mock implementation right over here nothing special special but at least it is returning with something so we can verify that data. password is uh storing the correct return value okay so now the next part we want to um test this part over here you see how we are creating a new user so this is where we are invoking the Constructor of a class so now you're probably wondering well how do we test that so what we can do is we can actually mock these es6 classes it's actually not that difficult and I'll show you how to do it so what we can do is right over in our test file up top over here I can go here just. Mock and I can just pass the path to our our user right over here this this user. MJS fult inside mongu schemas so I need to go out One Directory to mongus schemas user. MJS okay and then up top over here you know let me remove this mock users import let me import user from the uh mongu schemas user. MJS file okay so I just mocked that whole module right over here and now what we can do is so I uncommented this line over here where we call the new user Constructor and I can write an assertion and I can say expect user so I'm referencing the entire model which is a class and that's coming from this UT right over here okay this is our model right over here and then what I'm going to do is I'm going to use the two have been called assertion and I'm going to run the test and it's going to pass so this verifies that the Constructor was actually invoked if I comment this out out you're going to see that it's going to fail because it it actually was called or it actually wasn't called but we wrote the assertion to see that it was called so let me uncomment that and let's just change this two two have been called with the correct argument so we want to make sure that it was called with the correct username display name and the hashed password okay so in our test let's go back up over here so notice how this is the return value of matched data because we are passing uh matched data whatever this return value is into the user Constructor only with the password being hashed so what I can do is I can pass in that object and just change this to Hash hashed password and remember this value comes from our mock function over here we just prefixed hashed underscore with whatever the password is so let's go ahead and run the test and you can see that now it passes so that's good and now one more thing that I also want to show you is if you wanted to access the instance that was created so the user instance you can do that by referencing user and it's going to attach this mock property onto that user and then you can reference instances at subscript zero because we only created one instance and then let's run the test again and you can see right over here we have the instance and this instance has this save method as well and you could of course also reference the properties on it so let's go ahead and continue testing the rest of our code so I'm going to uncomment this out and since we've already mocked out the user model or mongus model I can actually just run my test and we shouldn't get the same errors that we had before because all that is mocked out already okay this save method is mocked everything so what I want to do is I want to verify that the save method was called on the instance this new user instance so what I can do is this I can actually create another spy on this save method so I can keep track of its calls because there's no way for me to directly access this unless if I get the instance so for example if I wanted to I could do user. mo. instances 0. saave to have been called like that and this should work without any problem yep and then if I try to negate this call to verify that it's not passing incorrectly you can see that it actually was called so this is one way that you can do this the other way that you can do this is you can use a spy and I'll show you how to do that so so I'll create a variable I'll call this save method equals just. spy on and I can reference user. prototype like that and then what I can do is I can specify the actual method itself on the instance so save is one of the methods and then leave it like that and what I can do is I can go ahead and replace this part to be expect save method to have been called okay and if I run test again the test will pass and if I try to add the not operator in front of it you can see that by adding a not operator the test fails because it actually was called so what I want to do specifically is I want to actually mock the return value of save so what I'm going to do is at the end of just. spy on I'm going to call mock resolved value once and I want save to just return the user that should be saved to the database but I'm just going to attach an ID you don't have to do this but I'm just going to do it anyways and then I'm going to copy that so that way like the reason why I'm attaching the ID is that way it makes it feel like it's being saved to the database even though it's not because mongod DB generates an ID for us so now what I'll do is I'm going to go down over here and what I'm going to do is I'm going to let's see so we are asserting that save was called so now let's write an assertion on response. status we'll verify that it was called with the status code of 2011 and then we'll verify that the response object was called with the send method or call it called the send method with the saved user which means that it includes that user over here that we are mocking the resol value so let's do expect mock response dot status to have been called with 2011 and then we just go down here let me change this to mock response. send and let me just copy this object paste it here and we should be good so let's test this out let me run my test and you can see that our test passes so that's good all right so we've covered two cases so far for our crate user Handler let's go ahead and cover the last case this one's going to be pretty easy we just need to get it to actually throw this error so here's what we're going to do we're going to go ahead and create a new test so I'll go ahead and call this test let's see um it should throw error when save or let's see it should send a status code to 400 when save uh errors out okay so everything else is good except for when uh the save method is called maybe something failed database and then it throws this th throws this error and it sends a status of 400 so let's do should send status of 400 when database fails to save user so I think that's good enough and let's make this an async method so we're going to uh let's see we're going to copy this create user Handler right here so everything is going to be roughly the same as the previous test so I do also want to copy this just. spyon because we do want to make sure that we are going past this part we're going all the way down but then it we just want it to fail when it calls the save method okay so the thing is though we don't need to rewrite all these uh assertions because we already did before so let me do this um con to save method yep so let me copy this save method equals just. spyon let me copy this part and now what we are going to do is we're going to go ahead and for the save method for this spy we're going to mock the implementation one time and remember this save method throws it could throw an error but we're just going to mock the implementation so when it does throw an error then it's going to handle this catch block over here but since this save method uh is asynchronous it returns a promise so we have to do promise. reject and we'll just say say failed to save user okay so now we're basically overriding the save method to actually error out return um return an error so everything in our code should be good and then once it gets to this save method it's going to go ahead and error out and then we're going to go ahead and write an assertion on response. send status okay so let's go ahead and do this let's do expect save method to have been called and then what we'll do is we'll do expect mock response that's send or I'm sorry send status right over here to have been called with 400 and let's go ahead and run our test let's make sure that it works and there we go we have covered all three cases if you want you can go ahead and write some assertions for some of these functions to verify that they were called but you don't really have to is it would just kind of be redundant with what we have up here cuz at this point in this test we know that all these functions are being called it's just that we're trying to get to the point right down over here to make sure that we are testing for uh this send status being called with a status code of 400 and if I were to pass 401 that test would fail because it is supposed to be 400 and there you go so hopefully all of this makes sense I know this this part of the tutorial was pretty long but that's because there's a lot of stuff that I wanted to cover and I felt like with this specific test you learned so much with how to mock modules how to mock classes how to mock your own modules that you create yourself on your uh on your in in your source code so your local modules um and just a lot of stuff about unit testing so hopefully this tutorial made sense and I would highly encourage you to practice writing more tests more unit tests for your other functions as well so for example one thing that I can encourage you to do is maybe for the local strategy if this is something that you have you can go ahead and take let's see you can probably take this part out you can take this Anonymous function out put it in a separate file and import it in here and then that way you can actually import that function into a test file and test the logic in here and using the logic that I showed you with how to Mock and how to override values of functions by mocking them out you can write those unit tests so hopefully all of this makes sense
Info
Channel: Anson the Developer
Views: 5,571
Rating: undefined out of 5
Keywords: express, express js, unit testing, jest, express js unit testing
Id: K-9IPd3oAoo
Channel Id: undefined
Length: 84min 39sec (5079 seconds)
Published: Wed Feb 28 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.