Nest.js Unit Testing | Best Practices + MongoDB

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey everybody today i wanted to make a video on how i go about writing unit tests for my next.js applications i may make another video about integration testing in the future but the idea behind unit tests is that we're going to test each file individually and mock any dependencies that file has so we're strictly testing the code in that one file and the idea being if we have sufficient unit test coverage on every file in our application we should expect things to work in the way we want which is obviously important and as always this project will be on github um i've actually branched off of a previous project i've done in sjs so i'll include the link to that in the description and on that repo i've checked out a new branch called unit testing so you can see there's abstract repository and unit testing so you can just clone this repo and then check out the unit testing branch if you want to follow along so let's go ahead and get started so this is the js project that i have um and important to note this is the one we've set up with the abstract repository so i'm going to show you how to unit test this abstract repository as well today so the first thing i like to do is so so we're going to have a test folder in each of our separate domains so our users folder here we're going to have a test folder and i like to host all of our test files in this folder so we keep the actual logic of our application separate okay and now in this test folder i'll go ahead and create a test file for each one of our files one for our controller repository and service so let's go ahead and start with our controller so if you remember our users controller is you know defines the routes for our application and will interact with our user service now as i said before we're going to be mocking the user service this dependency and really just testing that this controller is calling the correct methods on the service with the information that we expect so let's go ahead and do just that so we'll go ahead and create a new file here called users.controller.spec ts now inside of here we're going to be using jest for our testing framework which comes right out of the box in sgs so we'll have a big describe block that wraps everything and we will describe users controller and provider callback here okay so now that we've set up our describe block we're going to go ahead and create a before each block and so this is going to be an async block and what we're going to do here is we're going to get the ref the module referrer we're going to create our testing module which is similar to how we set up a module a user's module but in this case we're just going to instantiate the things we need to get the user's controller so make this a bit more clear we'll call this module ref and we are going to call test from nestgs testing dot create testing module and this looks just like a normal module from nest so you'll have an import section and then we can define the controllers that we want to instantiate so in this case we know we have the user's controller that's the one we're testing and now importantly we can provide any providers or dependencies that this controller requires now if we go into the user's controller we know that we are expecting to inject the user service so we need to provide this appropriately so let's go ahead and provide the user service next we're going to call compile on this so we actually compile the module great so now that we've done this we need to step back for a second here because as i said before we want to be mocking the user service here we don't want to actually be testing the implementation of our user service we're going to do that separately so in order to do that we need to create a mock or a fake user service that will still have all these methods but we're going to have dummy values that will be returned that we can spy on in order to ensure that they do get called and they get called with the information that we expect so there are many ways to set up mocks in sjs and and just the way i like to go about it and the way i found it to be cleanest is to use auto mocking and auto mocking is really great because it allows us to find one mock for each file and then we can use it in multiple places so in order to take advantage of auto mocking in just we create a folder at the root level of where we want that mock so for example we want to mock the user service so we're going to go ahead and create a mocks folder here that is directly adjacent in the in the project structure to this user service it's going to be in the same level of the user servers that's very important and notice the naming structure for the mox folder it's two underscores mocks two underscores which is important for jess to pick it up now inside of here when we want the same exact name as our user service down here in order for uh jess to pick it up and now what's really great is we we can export a const user service here and we want to have the same name in order to not confuse jest at all user service and then we set it equal to the mock that we want to create and what's what's really useful is we can call jess.function.mock return value and we can we can return an object that has the methods that we want because if we think about it the user service really is just an object with methods on it and just allows us to mock an object and we can supply and we can supply these dummy functions that return information that we expect but what's also really great is that the functions the dummy functions that we define here are automatically going to be spied on and we'll see why that is helpful in a little bit so let's go ahead and take a look at the functions that we're going to need to mock here so if we look at the user's controller there's a get user by id okay and very similar structure we're going to call just dot function and here we're going to mock a return value and now we need to think about you know what's the data that we expect to be mocking well if we look here we know that get user by id should be returning a user so let's go ahead and actually look at what the user looks like so we so we can see here that this is our user that we've defined our user schema now we want to define some sort of uh stub data a stub user that we can return from our our mock here and that we can also expect to be getting back in our controller so in order to do that i like to create a separate folder in the testing folder called stubs and here this is where all of our stub data or all of our sample data that we're going to be using in our test can be hosted in one spot which is pretty useful so in the stubs folder i'll create a new file called user.stub.ts now we could just outright export an object like this we can export user stub we know it's going to be of type user and we can we can fill it out here so let's go ahead and do that so this is just you know dummy data that we're going to be passing around now we could do this we could just export a cons here but the danger is that you know objects are passed by reference so we have the potential danger of you know if we export if we use this in a lot of different places and someone mutates this object it could um have unexpected results so instead of doing this i'm going to export a function actually called user stub and this function here is going to return we're going to go ahead and return the user object and so what this will allow us to do is it'll allow us to every time we call this user stub looks like we just need an extra bracket here every time we call this user stub function it's going to give us a new user stub object so that we don't share it between our tests which is pretty important i just realized my return type actually needs to be over here so now right every time we call this user stub function we are going to get a new reference to a new user stub object so we don't share that between our tests so that's great so we have our user step here now we go back to our user service mock and forget user id we can simply call user stub and now we're returning a new user object so let's move on to see what other functions we need to mock we have get users that's going to be very similar in fact i'll just go ahead and copy it down here and this will be called get users the only difference now is that we're returning an array of users so you can simply declare an array literal there next we have create user and we can mock another return value i believe this also returns the user and one other thing i've noticed too is that we're actually returning promise with a user wrapped inside of it so so if you wanted to be even more clear you could change this to mock resolved value which just states that we are going to resolve this the the return value because it's a promise but either way it'll still work so lastly we have one more function here and that is update user and that's going to be just the same as create user so now we have our user service properly mocked we have all of our functions that are returning our stub data and we are ready to go so let's go back into our users controller test and now we need to tell jess that we want to mock our user service and this is quite easy to do which is one of the reasons why i like auto mocking it's just a one line call we need to call just.mock and we just need to point to where the user service is so that'll look like this users.service and that's all we have to do that's great so now when we run this test and we provide the user service to the providers right here it's gonna just is gonna actually instead of supplying the normal user service it's going to supply the mocked value here and this is great because we can do this anywhere where we want to mock the user service very easily instead of having to define mocks in multiple places we are hosting it in one place okay so the last thing we have to do in this before each block is actually get references to our users controller and our user service so we can write some tests for them so i'm going to declare two lats up here users controller and the user service so this is just so we can access them anywhere in our testing file we don't have to keep redeclaring them and now before each block we'll set our users controller equal to moduleref.get we'll define the type it's a user's controller and we just pass in the type so we'll do the same thing for the user service here okay and one last thing i like to do in the before each is called just dot clear all mocks which if we look at what it does it goes ahead and clears properties of all mocks which can be useful to do if we if we've maybe modified a mock to return a value for us for one test only this will go ahead and clear that before each test which can be helpful for for dealing with some tricky bugs okay so now we've set up everything for our unit test and we're ready to actually test our code in our users controller so if we go ahead and look in the user's controller we're going to want to write a unit test for each one of these functions and it'll be pretty basic to do because we only have one line of code in each one of these so first let's start off with the get user function so i'm going to go ahead and write a describe block we'll call it get user now there's many different ways you can write your unit tests i really like the style of given when then derived from martin fowler i believe where we do our setup code in a given and then when blocks and then we can actually do our tests in the then block so it helps us separate things out sometimes it can be overkill but i'll go ahead and show you uh what that looks like so we'll have another block here and we'll say when get user is called and now in this block this is where we'll actually do the setup work and call the function one of the important principles for unit tests to keep them clean is that we want to write or we we want to do one assertion for each one of our tests and so this structure allows to do that pretty nicely so there's two things i want to test here about this method the first of which is that when we call it i want to make sure that we are calling get user by id on the user service with user id that i've passed in and then i also expect that we return uh a user and that has the same user id so these are two different assertions i'm going to write two different tests for them but the setup code will be the same so let's write it before each block here and this is going to be async because our method is is asynchronous and inside of here this is where we will actually call our function so we're going to call await user's userscontroller.getuser and this is where it expects user id so i'm gonna call stubuser or userstub and i'm gonna call user id so that we're gonna pass in a user id now in a later test we're going to want to test the return value here so we'll create a let up here called user of type user and we'll set the return value here equal to it so now we have the user so now that we've gone ahead and set up our test we can make our assertions so this is where our then block comes in so now i'm gonna test um then it should call user service and let's test that so what's great now is that since we've already set up our mock with these with this jest.function it's automatically spying on this and we don't need to do any more setup to to spy on these methods we can we can do it directly in this test now and so it looks like something like this we'll do expect user service dot get user id two have been called with and now we'll call it user stub dot user id and if you wanted if you're using the user sub a lot for the one test and maybe you've modified it in some way you could you know set the user stub equal to a let up here but in this case um we're not doing that so so this is sufficient so now we should expect the user service to get called and let's go ahead and actually test this out so we'll open up a new terminal and we'll do uh i believe it's yarn test users dot controller and we'll see what the results look like so it looks like we have an issue with the user service being found and so let's just make sure so it looks like we actually only need to go up one directory here let's save that and run again so now our test is passing and another good thing about this given when then structure is that it's pretty clean when you read the output so we can see user's controller get user when get user is called then it should call user service and we'll have a then block for each one of our assertions so let's go ahead and do another one for the return value so we can say then it should return a user and now we can assert that the user that was returned from our users controller call to equal and we'll call our user stub so what this is doing is it's asserting that uh the return value from this method is directly from the get user by id which we've mocked already so we know that this should be our user stub and now we can assert that which we've done in our users controller so we can go ahead and run this test great so that's going ahead and passing so now that we've set up a test for the get user function i'm going to go ahead and challenge you to write three more unit tests one for the get users one for create user and one for update user it should have a pretty similar structure to the one we set up here you should test that not only are we calling the service with the correct parameters but that we are calling that we were receiving the expected value from the service as well so go ahead and try writing some tests for these um i'll go ahead and show you what mine look like after you finish all right so i've gone ahead and finished adding the unit tests for the remaining three functions in our controller so our get users test here looks very similar to the get user one the only thing is now we're expecting an array of users so down here we're expecting that user service dot get users is called and that the users up here is a array of our user stubs now next great user is a little bit more complicated i have two let's appear one for the user that gets returned when we create and the other for the create user data transfer object that we pass in to create user in the controller now i've built this create user dto because it's only a subset of the stub user we have but we can still use the same properties so i have the email and the age we pass it in here and then when we expect that the user service dot create user is called we can use to be called with and expect that we're passing in the crazer dto email and age as well as expect we get the user stuff back lastly we have the update user and this looks similar to our create user we have the update user dto and now we have an age and a favorite foods and now we call update user we pass in the user id of the user stub and the update user dto and then on the update user function in the service we expect to be called with our user stub user id and the whole user dto object and then lastly we expect that returns the user stub so these are all the unit tests for the controller now let's go ahead and move on to our repository now our repository is going to be a little bit more complex just because of how we have set it up and how we're extending our abstract repository but it's not terribly difficult so let's go ahead and get started so i'm going to go ahead and create a new file called users.repository now i'll create my big describe block or i call it users repository and so just like we did with the users controller we'll have it before each block that is async so we can set up our module reference we'll declare module ref here and we'll set it equal to await test import it from nest guest create testing module now we're going to have to provide an array of providers here we don't have any more controllers now but we do have providers and obviously the provider that we're trying to test is the repository itself so we're going to have to provide that to nest so it can instantiate it now importantly if we go ahead and take a look at our user's repository we'll notice that we are injecting uh we're using the inject model tag here from an sjs mongoose and passing in user.name which is the name of the user object that we have so we need some way to mock this injection that nest is doing for us and the way we go about doing that is we can create a custom mock for this provider or not a mock but a we we can override the provider so to do that we specify which provider that we want to override and we call this function get model token from nest js mongoose specifically for this testing purposes where we can get the token of the model which is how nsjs identifies it in the context of a module and then we just need to pass in the user.name similar to how we do it here with this injection and we'll have to import user as well and now we'll specify use class to tell it uh which class we want to use to to mock the functions on the model and by the functions in the model i mean if we go ahead and actually look at the abstract repository so the functions here that we're actually calling on the model like find find one uh save find one update that's the the mock that we want to override so we want to provide a mock to use that for now we could create one mock in this user's repository and replicate it for every test repository we have but i prefer to follow our abstract approach here we have an abstract repository so let's create an abstract mock that we can use and set up easily for our repositories in the future so to go ahead and do this i'm going to go ahead and in the database folder that we have i'm going to go create a new folder here called test because this is going to be a test related file and i'll create another folder called support in case that we you know we actually do have test related files i want to keep this out of that scope and now in the support folder i'm going to create a new file called mock.model.ts so this is going to be our mock model so let's go ahead and export an abstract class we'll call this mock model and this is going to take a generic type which will be of the types uh the stub the stub type that we are passing in or instantiating in our subclass so we'll go ahead and declare it here in abstract property entity stub which is going to be of type t now we don't want to provide this through the constructor and we'll see why in a second here and the reason being is because we're actually going to use a constructor for something else and this is how we're going to be able to spy on the instantiation of a new model so if we look at the create function of our abstract repository and we see what's going on we first instantiate a new entity model so a new model and then we call save on it so when we're testing this out we want to test not only that the save function gets called but that we are in fact instantiating it with the data that we expect and i found the easiest way to do this is create a constructor in our mock model where we expect to get that data of type t and then we call a a kind of dummy function that we can in fact spy on so i found this to be the easiest way about going about this maybe maybe you have another way so here we'll declare the constructor spy and this will just be a useless parameter and nothing really goes on here but this is just something we can spy on to make sure that the constructor did get called with the data which is important in the context of the model next i'll clear my find one which is going to return an object with an exec function that will return the entity stub so let's go ahead and return just that so it's a type t and we're going to return this dot entity stub so that's the find one the find is similarly going to return that exact function but it's going to be an array because we have multiple stubs being returned so exact this is going to be an array and then we'll just pass an array of our entity stubs so the last two functions we have to do are async save which in this mock is going to be a promise of type t and we can just return this.entitystub and lastly async find one and update similarly it's a promise type t and we'll just return this dot entity stub so now we have an abstract mock model in our database folder so now what's great is if we go back to our test folder in the users directory we'll create a new folder here called support right and so now this is separate from our test files separate from our stubs it's a support folder and we'll create another file called user.model.yes now to set this up all we have to do is declare a new class called user model and we'll say it extends our mock model pass in a user and now the only thing we have left to do is to provide our stub so that our mock knows what to return uh and so this is going to be a user stub great so now our model is all set up and see how easy this is to do if we have multiple repositories so now if we go back in here for our use class we can provide it the use we'll call compile on this and so we'll get a reference to the user repository as well as the user model so we can perform tests on it all right and we'll get also a user model which will be of type user model so let's get both of these with our module wrap lastly i'll call that clear all mocks that i like to do between all my tests so let's go ahead and create our first test so let's uh write a describe block for our find one and we'll say when find one is called well we know that we're going to have a user here so let's declare a let and let's set up our tests so we'll have a four each with an async because it's going to be an async action that we have to call so now uh we're going to want to spy on the user model that we have set up and what the spying does is it just watches um it tells jess to watch the function on whichever class we provide it tells it to watch it so that we can later expect to it have been called with args or or certain amount of times so we didn't have to do that in the users controller spec and that's because we had already created this mock here and by default when you create a function with this just.function it automatically spies on it for you in this case we don't have that so we need to tell jess to to wait and and look at this function so that we can expect it later on so now we'll actually call usersrepository.51 and we're going to need a filter query now i noticed that this filter query is something that's going to get used uh quite a lot in our tests between our tests so what i'll go ahead and do is i'll actually declare it up here as a let user filter query and we can say filter query of type user and before each we can reset this in case it it got modified in any way so this way don't we don't have to keep uh redeclaring it but each test it's going to get reset so now up here we can just pass in our user filter query okay so as we expect we should then call the user model so we'll expect our user model dot find one two have been called with our user filter query and this is why we had to spy earlier because otherwise just wouldn't be watching this function for us but now it is and lastly we'll expect that it returned a user so in this case we'll expect that the user we got set to up here and our before each block is equaling our user stub and we can call this user stub here and remember this is because in our our mock our user model we provided the user stub as the entity stub and we're simply just returning it as part of our exact function here so if we run this test we see it it did fail because the find one is actually receiving some extra properties the version zero and the id is zero and this is because when we set up find one we can see we actually told it to to not project the id in the version this is good it tells us that our test is working properly but it also means in here so here in order to be called with we'll provide a second object pass the id of zero and the version of zero because that's what find one actually it's called with so if we go ahead and run this again you can see everything's passing now which is great so find one that's very similar to find so i'm just going to go ahead and copy the whole block and change it to find all right the only differences that are going to be so going to be array of users we'll have users we're going to spy on the find function change this to users change the actual call but the filter query will remain the same we're still looking about id and now we expect that find is called with the user filter query and there's no projection in this case and then users should be an array of user stubs so let's see if that worked properly if we quickly rerun this so it looks like the problem here now is that it's returning the exact function from our find call so if we go ahead and look at our user model and our mock model it looks like we don't actually need to return the exec function here we can actually just return the entity stub an array of entity stubs let's go ahead and do that which will retain return our change our return type here to uh i believe it is async so let's go ahead and look at the entity repository yes we don't actually call exact here we're going to turn this into an async function return a promise of type t and that's all it'll look like so now let's go ahead and run our test again it should work now great so now our tests are passing all right so two more to go let's do the find one and update i'll just copy this block again so we have a similar structure but now this is going to be describing find one and update when find one and update is called well we're only gonna be getting back a single user now um we're gonna be spying on find one and update this whole changes back to user we're going to be calling find one and update we still pass in the user query but now we need to pass in updates so update ntt data uh in this case i'll just pass in the entire user stub just to make it simple we still expect to call the user model but now we expect to call find one and update so it should be called with the user filter query it should then be called with the updates we provided it so the user stub and if we take a look we also are doing the new property so we expect it to be called that as well so new should be set to true here which just means we're returning the new entity and then user should be the single user sub so let's go ahead and run that one more time and that looks good all right so the last unit test we have is for the create and the create is going to be the most complex of all these because of how we're doing it in the repository so i'm just going to copy the find one update block change it to create we're still going to have the user here but there's a couple of other let's that we're going to declare the first of it is going to be the save spy which is going to be a jazz spy instance and then a constructor spy constructor spy which will be adjust that spy instance as well um we'll see what we're going to do with these in a second so if we look at what's going on in the entity repository we can't actually spy directly on a function like find one and update or find we can spy and create and expect that it gets their data but really what we're more interested in is that uh we are calling or instantiate instantiating the entity model here with that data so that's really what we want to spy on so in order to do that we'll go ahead and get rid of this and we're going to set the save spy equal to just spy on and then we're going to call user model dot prototype so we're going to spawn the prototype of it and the save function so in the user model we have the save function here and the reason why we can't spy on it directly with the user model that we've defined up here is because this user model that we've declared up here is the one that nestges has injected for us but in the case of our test we're instantiating a new model so this is a completely different object so the save function on here it's going to be different so we want to spy on the prototype of it so that really any any object that gets instantiated that the new object that gets instantiated we can look into it uh and similarly for the constructor spy we'll do something similar we're gonna spot on the user model prototype and this will be the constructor spy function that we saw earlier now our user here is going to be set to await user repository dot create and we'll pass in our user stub here so now we still expect the user model to get called but now we'll expect that our spies so save spy gets called so the save function got called and our constructor function our constructor spy got called with our our user stub so this is really great it it tests that we've instantiated the new model and then called save on it which is really what we care about mostly here and then lastly it should still be returning a user so let's go ahead and run this test so if we go ahead and run the test now we're going to see that we're actually running into some errors about this.entity model not being a constructor and in general the setup is kind of blowing up and the reason for that is because we need a separate module for our crate operations and for our find operations and to be perfectly honest i'm not i'm not completely clear as to why this is i i've kind of chalked it up to how uh nest is instantiating uh this the the providers at runtime um but to show you what i mean here let's create another block called describe and this will be our find operations and simply all we're going to do is we're going to keep the user's repository uh in the upper scope but we're just going to move all of our find related code into this block here okay and then we'll just in you know fix the indentation so now all of our find operations are scoped uh here and we'll do the same thing for create we're going to create a new block here called create operations put in before each block so it's really the same thing as above for our find operations but now we're going to move this crate inside of here and we'll go ahead and indent this we have two separate modules being instantiated for our crate operations and for our find operations and like i said i'm not entirely sure why this is necessary i've just figured it out through experimentation so if you have any ideas please do let me know in the comments below so after we run it we see everything works as expected so this is great this is how i go about unit testing my repositories and all of my other files as well so we didn't do the users service but that would be good exercise for you to try out you know creating a repository mock here similar how we did to the user service and trying that out i plan on doing another video about integration testing next so if you'd like to see that make sure you're subscribed and stay tuned thanks
Info
Channel: Michael Guay
Views: 43,312
Rating: undefined out of 5
Keywords:
Id: 1Vc6Xw8FMpg
Channel Id: undefined
Length: 40min 38sec (2438 seconds)
Published: Fri May 28 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.