NestJS Testing Tutorial | Unit and Integration Testing

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
good morning or afternoon or evening wherever you're coming from uh today i wanted to make a quick tutorial video on how to do testing unit testing integration testing with nsjs so the code i'm starting with today is actually the code i had from a recent video on the nesjs versus adonis so this is the the nashjs app from that video you can also watch the nesjs crash course that i have as well as my typewrm video and you if you kind of merge together what you learn from those two you effectively will have the same application but if you didn't see any of those videos but you have some decent understanding of nest let me just go over this code real quick so basically we've got ourselves a user's module and within that user's model you've got a user's controller and a user service and we're also injecting our repository for a user's table in our database you'll see in our users controller that basically we just have the basic crud you know your typical create read update and delete correct right so what we want to do here is uh figure out how we can unit test the controller the service and then later after having some time we'll also cover our end to end testing integration testing so if you've seen my previous videos you'll notice that i often use the nscli to generate a lot of the the files and that's something that i highly recommend you also do because it not only enforces a nice folder structure but it also creates the test files for you automatically and so it gives you kind of an initial skeleton to to work with so this file right here i didn't write it from scratch it was generated by the cli before you get started with testing like this ideally you know a little bit about jest because this is using just behind the scenes you know when you run your test script that's really just running just and uh you know you need to be able to know that the syntax suggests as well as how to do um you know basic mocking and spying and doing assertions if you haven't used just before i mean i think it's pretty intuitive you'll see a good amount of examples in this video so maybe just follow along alright so first let's talk about this bit right here what's happening so what's effectively happening is as you can see nest is very much driven by um this grouping mechanism called modules and what we're really doing here is we're effectively sort of think of it as we're creating a fake module on the fly kind of like how we have a user's module here which we can put this side by side right this user's module has a controller and a user service and we're sort of on the left here kind of just trying to simulate the same thing right so you can see that we have our controllers and our providers and we're really just trying to simulate the i kind of think of it as the the minimum module that you need to run your tests so for example if we were testing the controller right the user's controller um that really only has one dependency which is the user service as you can see in the constructor here and sjs is during runtime would automatically inject the user service and if we look at our initial testing module over here you'll see that that's all we have we've got the user's controller and the user service and we're effectively kind of simulating a isolated module for this one test all right so let's go ahead and run our test script specifically i'm going to do test watch so that as i change my code it's going to re-run for me and there's a lot of failures here because i didn't write the test yet one of the things that i like to do with jest you can see here if you press p to filter by file name you can sort of have it focus on the file that you're currently working on so in our case we're gonna do users controller right and it's failing on this one test it should be defined and the reason this is failing is because it's saying that it can't resolve one of the dependencies of the user's service uh which we'll get to because if we look into the user service right this one has this uh repository dependency which we use to connect to our database but we're not providing that in our fake module here right you think of it as we're not running the real nest application we're just making we're just running a fake module now with a unit test typically what you want to do is you know as the name implies you just want to test your smallest unit possible and you generally want to mock your dependencies you don't want it to kind of integrate things that you don't necessarily want to be integrated so in this example i don't necessarily care about the dependencies of the user service i just want to be able to test the user's controller in isolation without kind of dealing with the real implementation of the user service so the way to get around this is we need to be able to mock the user service so that we kind of simulate a fake user service that we can use in our test which wouldn't involve the real dependency of the user service all right so to do that we're gonna introduce a new let's call this mock user service and i'm just going to make this an empty object for now and down here what you can do is you can add override provider right and you can see there's different types of overrides right so if you've got a guard or an interceptor and a pipe that you wanna override you can use one of those but this case we specifically want to replace the user service that's being injected into our controller so i'm going to do override user service and then i'm going to say use value and similarly here there's there's three different approaches right that this is really up to your um preferences you know you can provide a class and it's gonna instantiate that class for you you can provide a factory function i like to use use value because it's just simple and i can just provide a basic object kind of like this one so we're just going to copy and paste that in in there and all of a sudden our test is passing right so now uh it's able to it's basically telling us that it's able to instantiate our user's controller and it's able to bring in the user service dependency and it kind of just stops from there right it's able to instantiate right it's able to compile in isolation so generally with unit testing that is the first step you take a look at the thing you're testing in the constructor what are its dependencies right and it's good to have a little bit of uh fundamentals on dependency injection uh but if you don't just think of it as pretty much anything in a nest app that is defined as an argument for the constructor is a dependency that nest will automatically inject into that constructor as it kind of builds instances of your classes so it kind of handles the dependencies for you and kind of injects an instance of those things into your into your class right and in your test your job is to just basically make sure that you're providing a proper instance of those dependencies whether or not they're mocked it's up to you to decide um which ones you should mock and which ones you should use for real now like i said generally with unit testing you just want to test the unit you don't actually want to test them integrating and they're grading together so it's often likely that you're going to mock your dependencies on a unit test all right so we've got this one test we're able to basically instantiate our controller but that's not really useful right let's let's add a more useful test in here so if we take a look at the implementation of user's controller what do we have in there so on the right here we have a create method that simply calls user service that create so a basic test for that is simply you know we could do a basic it should create uh create a user you know whatever message there that is meaningful to you and you'll notice that in the left side here there's a way for us to kind of get a a reference to that controller that got created over here and what we can do is we can do something like expect controller dot create right and you can see it auto completes the methods that i have because of typescript and such so create and it's expecting a create user dto so you can provide an object that has that shape let's take a look at there it just requires a string so i can provide a name i'll just give my name in here and then we can make assertions on what happens when you call this so you for example maybe you want to say that this returns a a new object that has an id and a name right maybe we do something like uh id so we just we can do something like expect that any and i can pass a constructor in here to make it more specific so for example maybe let's say any number and you know obviously if you're creating uh with this name over here we should expect that to also come back as the name of what's what got created right and initially that's failing because um our mock doesn't do that currently if we take a look at user's controller we see that it's simply passing the dto down to a user service docker 8. so what we can do is in our mock user service up here we can simply create a method called create that is a mark like this right so this gives us a fake implementation of the create method and it also allows us to kind of provide a you know implementation like so so for example maybe i want to take that dto and i wanted to return a object with uh whatever was in the dto and i also want to generate some kind of id right maybe let's just do that now right and all of a sudden our test is passing because what's happening right we're calling our controller.create within the controller that's going to call our user service that created passes our dto down to the service and our mock service is taking that dtl and just adding an id to it right we're kind of fake simulating a a database insert and an id being created all right so that's really the basics of it um one might also ask so another way to of doing this is you can also instead of overriding the provider you can use the real one but use uh spice like just just spies the only problem with that is as you can see it you need to set up all of the dependency chain to be correctly in this fake module um which means you'd probably have to deal with the dependencies of user service in this test which you generally don't want to so at least from my experience it's often easier to just straight up mock the entire service the entire dependency and then provide a mock implementation like this you know another thing we can add here if you want is you can do assertions like you know our mock user service.create that to have been called right and that should still be passing uh you know you can even you know it's up to you how you really want how specific you want this to be so for example you could also assert that it got called with this with this object right right now it's gonna fail because i didn't provide anything in there it's saying i expected this this method to have been called with this object right so if i provide that here it should go back to passing and you can do a little bit of cleanup if you want like you know put your dto in a uh variable like so so that you can kind of reuse it throughout your test which is kind of nice right you can do something like that and everything's still passing it's good all right so generally that's really that's the basics of it it's it's uh the unit tests i think are pretty simple basically you just instantiate whatever you're trying to test and then provide a dependency provide the mock of that dependency as needed and then sort of just simulate calling every method that you have so if i were to keep going with this right you you'd see a test created for create which we just did you'd see one for find all find one update remove let's do a test for our update here because this one has a lot of things in it let's again put this to the side so you can do something like it should update a user and what we're going to do is we're actually going to go ahead and do our mock first this time around because it's just going to be simpler let's add an update just that fan so the the implementation of this the mock implementation of this is pretty simple basically we just want to create a user object that has id which we passed in and the name from this update right so you can do kind of similar here so you'll notice that over here on the update it takes in two parameters right so we can do similar thing we can do id and dto and we just want to return basically an object that has that id and whatever is in our dto okay and by the way uh this is one syntax to do it with just you can also do dot mock implementation um i don't really know there's a benefit to doing it this way ah but fyi you can do it that way too all right so we got our update which basically just returns a fake user that has the updated name so what we're gonna do here is we're gonna do the same thing we'll take this dto and we'll do expect controller dot update and in this case it's requiring a string id and a dto so in this case we'll pass in maybe one you know whatever string id mock id you want to provide in there and then our dto and we expect that to come back with what we expected to have a shape like this right because you'll notice that the the controller is casting the string id to a number so you can kind of imagine that in our database we have uh integer ids right so in a real environment you can imagine that you call patch on slash users slash id with a request body that includes a name and it's going to return to you that updated user with that name that's really what we're kind of trying to test in isolation here right and similarly if you want to add that assertion that the service got called it's up to you i'm not really sure it adds that much more value but you can do that you know you can just assert that to have been called i think one reason i would probably leave these out is if you're trying to detach your test from the underlying implementation in the implementation details you know what if for some reason you switch the implementation to a different provider or service but it still comes back with the same result um in that case your test would be a little bit less brittle if all you had was this one and if they include that one but it's it's it's pretty typical that you're gonna use you know the the service in the same module so um that's not bad to have there either alright so there's a three tests for you i'm not going to implement the rest of these controller methods because i think it should be pretty straightforward right if you're following along i highly suggest you kind of use that as an exercise for yourself now why don't we go ahead and switch to our user service and see how that differs a little bit from testing right so our user service same thing it's a effectively just a class that has stuff being injected into it right as dependencies and then it's got its own methods all right so let's take a look at the user service dot spec file and you should notice that it has sort of the same structure and and let's go ahead and switch our tests to users.service right so now we're switching to testing just a user service file which we have here same thing right initially you kind of start with a fake module and you want to uh make sure you provide all of your necessary dependencies and mock anything that you should that you need to mock so in our example here again we've got a constructor and we see that there's a repository of type user being injected so it's it's important for you to know sort of how how do we simulate this injection in this test well really it it's really just another provider and we can do something like this and we're going to do provide get repository token of type you'll see that it's expecting a entity and let me type this out and i'll explain it a little bit more in a second so we want our user entity in here and then similarly to how we were doing you know use uh use value in our user's controller test you can see that this also allows us to provide a class or a factory or something existing again i like to use a value here because it's just simpler right and i'm actually gonna extract that out to a mock user's repository and i'll provide it in here all right so again you can kind of see that our test for it should be defined is now passing so that's generally kind of um your first test your first step with any test is make sure your dependencies are in place generally if you have missing dependencies as you saw in our previous examples it's gonna fail it's gonna say i couldn't construct this fake module because i didn't have everything that i was expecting right like user service user service was expecting this repository we didn't provide a repository so it was failing and now we're providing a repository this way and by the way this is sort of just a slightly different syntax from what we were doing in the usage controller over here where we we use override provider and use value this is just another way to do it this one specifically with mocking repositories it's just a little bit more straightforward because you have to do this get repository token basically we're trying to tell nest what is the type of thing that we're trying to mock and this is really like a special string that you that you have to provide um to nest so that it knows yes that is a type of a repository a specifically a user repository that you want injected right so um really it's the same idea as what we're doing over here just a different way of doing it all right so same thing kind of what we did before right where you were unit testing again the the user service in isolation so kind of similar thing let's say that we were trying to test this create implementation right and you'll notice that when you you create it requires a a dto and it's going to do some kind of create and then save so that means that our mock repository also needs to have a create and save method so before we go ahead and test that let's go ahead and improve our mock users repository to first have a create which is a just another just mock function which takes a which it takes a dto and effectively if you understand if you saw my previous type ram video what this grade is doing is effectively it's just doing um new user right and then it kind of sets the fields to whatever you pass into your dto so it would do like if this was user equals new user it would do user.name equals name right if you have name in your in your dto so we could kind of just simulate it to just return the same dto you could also maybe have it instantiate the actual class if you want and then our save again kind of similar thing mock implementation and kind of similar to what we've done in the controller what what this is really doing is it's taking that new object saving it in the database and then brings back that new record which in this case would have an id so we're gonna again simulate the similar thing where we do something like or really you can maybe do user here and have it return a new promise an object that probably has an id let's do another date there or you can put in just any number that you want and then again we're going to spread the user and notice that we're providing a promise here because we we're trying to sort of simulate the same thing right that this save returns a promise of a user and we simply return that so this entire method effectively returns a promise of a user right so now that we have our mock repository in place let's go ahead and start writing our test it should create a new user record and return that one thing that's a little bit different here is since we're working with promises we probably want to make this async all right so we can do something like expect await service dot create and what do we need there we need a dto with a name same thing to equal and we can do again so similar strategies we did before we can do something like expect that any number and uh this name right and we get a passing test right so pretty similar because if you think about it the the create controller method is really just calling this right so the mocks kind of kind of end up pretty similar but the difference is now you're you're mocking the the dependency of the repository the dependency of the user service which is the repository um and you're providing the mock implementation a little bit differently alright so again similarly if you want to you can add things like expect you know create and save to have been called but i have the same comment with this right is that you might not want to add too much of the the implementation details in your test because it's going to make your uh code a little bit brittle if you change the implementation to let's say that you switched your type or m code here to do an insert right there's also an insert method like this one repository then insert and then maybe you switch to an insert in query because insert doesn't return the inserted user so ideally this test is still passing even though you change the implementation but if you were asserting that these methods specifically got called then it would fail so so i would try to add just whatever you need to to kind of assert that your your method is working and don't don't try to add too much more than that all right so i'm not going to add any more tests for for these guys either i think you kind of get the point right it's pretty simple if you're following along again i suggest you use it as practice maybe see if you can implement the rest of the user service methods here now what i i want to switch to is we've done a couple examples of unit testing i want to switch over to integration testing and to end testing specifically so you'll notice when you create an app in sjs using the co light also creates you this this test folder that has the e2e tests in it and that also has a dedicated script which you'll see is npm run test colon e to e the only thing different with this script is it doesn't have that watcher like we have so what i'm actually gonna do here is i'm gonna take this script and i'm gonna add add a watch version of this so that we can use it uh for development so i'm just gonna add the dash dash watch at the end of it so it'll work similarly to this one but it's specifically adding our e to e test so let's go ahead and do npm run test e2e watch and now it's running our app e2e test so let's take a look at our the initial tests so this is what was generated initially when we when we use the cli to generate the application it comes with this initial example test and you'll notice that it's using super test you know my i think my last video was on tdd with express node.js and it's also using super test so that might be another good video for you to watch but super test essentially allows us to take our application and then simulate you know actual http um requests against that application and then we can do assertions like like this like when you call that path what does it bring back to you so what's a little bit different with the before each part of these e2e tests is that you'll notice after it creates that fake module it actually creates an instance of an application right if you compare that side by side with our user's controller test you'll notice on the right here uh we're simply just compiling the the testing module and we're getting a reference to the controller here it's actually taken in a an input of the root module and then it compiles that and then it makes an actual mini application out of it right so it's composing all of the the underlying dependencies of this module to create sort of a fake application on the fly which you can test uh real http requests against now what you can see in this initial test that we have here is that it's starting from the very root app module and it kind of attempts to create an application out of that you can definitely do it that way but i personally like bringing in modules individually because in a more realistic application right your app module is going to have multiple other imports in it and that kind of dependency chain is going to be really complex and you're going to have a lot of things to you're going to have a lot of things to mock so sometimes it's easier or more simpler to test if you think about it like slices of your application so rather than testing the entire app module maybe in this case you want to test just a user's module it's definitely up to you but i'm going to show you the way that i'm trying to talk about so in this test folder maybe i'll just create a new user dot e to e and we'll just follow that same naming convention dash spec.ts i'm actually gonna copy this code and bring it over to our user one but i'm just going to change this to user controller e to e and i'm also going to have my test focus on the user e to e right so in this case we're going to switch this up to specifically be the user module so think of it as we're think of it as we're trying to effectively create a mini application just starting from our users module alone so all right so what do we have here you know let's say that ultimately we want to be able to do a slash users get and we expect let's just start with we expect to get back a 200 right really simple test all right so we're just running a basic test and kind of similar thing you'll notice that initially it's it's going to fail for us when we haven't provided our dependencies properly so within our users module right it's got user service and within user service again it's got the repository but there's nothing in our fake module that's providing this resource repository so it's telling us hey i can't resolve this dependency so in that case we should be the ones providing it so like i told you you can use the override provider right similar strategy and the same way that we're providing the you know similar how we're providing the token here we're gonna copy this right and you can that's actually something you can also pass into here like that let's get our missing imports in and then we're gonna do use value mock users repository and similar strategy we can make that up here and real quick i don't know if this is just a problem with my setup but i find that sometimes when i do auto importing with vs code it imports it from the base like this that usually makes tests fail so you want to make sure to update that to be relative all right so let's review real quick what we have here we're saying we're instantiating our kind of our users module in isolation um but specifically we want to override one of the things inside that module which is specifically the user's repository kind of similar thing we're doing with the user service and then we're just creating that module and we're creating an app off of just that one module as if it was the root module and then once we have that app we can then start simulating http requests against it and actually testing the integration of our entire module from you know from controller to service to repository this is also a place where maybe some people might want to provide an actual database connection maybe like a an in-memory database of some sort maybe sql light that's up to you i find that sometimes it's a little bit simpler to just mock the the data layer because that's the um the last layer in the chain rather than incorporating in a real database it's up to you it's really depends on what you're looking for with your test coverage if you want it to be a little bit more real and there's less mocking maybe integrating a an actual database is more useful for you you know but one thing you have to think about if you incorporate an actual database then you also have to think about making sure that you run migrations on a database so that it has the schema that it's supposed to have so that parts a little bit more complex in this tutorial we're going to keep it simple and we'll just gonna we're mocking the the user's repository so that it still provides us an integration test that kind of makes our you know our controller and service and data layer integrating together so it still provides us a lot of value all right with all of that said notice that now our test is still failing but specifically because uh it can't find the find method on our user's repository so same thing if i start implementing that with just just that fn we should see that our test is passing right and what is this really doing is it does a slash users a get on slash users so in our users controller it gets over here and then it calls user service that find all which user service that find all simply calls user's repository that find and return something and that returns back a array of users so if you wanted to make this our test be a little bit more useful you know maybe we do mock result value mock result value is just uh a sugar syntax to mock implementation which returns a a promise kind of like what we did earlier so if you're doing mock.resolve and you're just returning a value like that um if you just do mark resolved value you know that is the syntactic sugar for that right and it actually says it right there uh some simple sugar for just fn what i just said all right so perhaps you want to do something like i don't know let's say you had an array of mock users right and if you want you can just return that and then you can make your test a little bit better here where you can do expect mock users right so you've got yourselves uh it's effectively saying that uh if i make a call on slash users using http verb get i should get status 200 and my array of users you can also add a bunch of other things in here like uh let me open up the super test documentation real quick so you'll notice you know you can add stuff like maybe you want to assert um you know the header and stuff you know like the content content type maybe you want to assert that it's json you can add that in there you know all sorts of things you can you can put in here so it's really up to you and also notice that you know you can do all sorts of of assertions as well on that on the thing that came back as a response so i'm not going to go over everything about super tests i highly recommend you take a look at the documentation for that but as long as you know how to set up your your testing module and have it run through super tests like that you know that's really the fundamentals that you need maybe let's do one more example of i want to be able to on slash users post so let's copy this and just change a little bit right in this case we want to do a post we still expect jason to come back but in our case uh we want to return 201 because that's the default status that you usually get with a post and sjs and then rather than using this base expect here i'm actually going to switch to a i'm going to get the promise result value of that response so that i can do something i can use the gest assertions instead so i can do something like response that uh body to equal kind of similar thing we've been doing with id um funny name just like neither all right so one thing that's missing right is we need to have a request body which you do via send which you do like this and you'll probably already know why this is failing right because we don't have our macro8 and save which you can actually just take this thing we did previously right in our user service mock repository let's bring that over up here and now our test is passing you can kind of think of it as we basically tested the same thing except in this case we're integrating our our components together we're making our our controller and our servers and our data layer actually work together why don't we add a little bit more of let's add something different right so in our users controller you'll also notice that in our create user dto we have validation in place the name has to be a string so we expect it to fail if uh you know maybe provide numbers and if you saw in my previous videos i talk about how to do validation pretty frequently so let's copy this and let's do [Music] i don't know it i think this returns a 400 if there's a validation error we'll remove this assertion we'll switch this to 400. and to make this fail we got to provide it with something is not a string in this case numbers and this is still failing because uh the thing that we use to enable our validation if you saw the previous videos is this global uh validation pipe what we want to do is we actually want to enable that as well in our test we should be able to just add that in here and now our test is passing right that means that it's able to see that hey this is a number it is again uh with the class validator it checks the dto against class validator that it's a string and then if it's not it throws a specific http exception in this case it will have a status of 400 right and if you want you can do let's just add an empty expect here to see what the response body looks like right so it looks like that on the response you can also you know copy that if you want to you make you want to make your test a little bit more specific right so it's back to passing actually there's also another way in the syntax with super tests is you can just add this as a second argument here on the and the initial status expect that's also equivalent right so how much you assert is really up to you and how specific it is um again it's up to you depending on how strict your tests want to be you don't you don't want to make it too again like i keep saying you don't want it to be too tied to the implementation details because you don't want your tests to be brittle you effectively just want to test your stuff to simulate as much as possible how would a typical api consumer use your api you know in this case we're kind of saying that what if a user user api and they provided a problematic non-string name we want to make sure that a 400 validation error is thrown all right so that's it for today folks i don't think i'm gonna do any more examples here again i didn't do the whole quad right if i did it might take two hours but i highly recommend you kind of do the other ones as practice if you're kind of new to this stuff but if i were to summarize right the basic gist of it is in nest because it revolves around inversion of control and specifically dependency injection is you just got to make sure that before you start your test you set up your dependencies to be injected properly mock any dependencies that you need to mock and you go from there all right hopefully you found that pretty useful uh make sure to let me know in the comments what you think if you have any feedback for me i'd love to hear from you and that's it have a good day and i'll see you on the next video [Music]
Info
Channel: Marius Espejo
Views: 23,402
Rating: undefined out of 5
Keywords: jest, javascript, nodejs, nodejs tutorial, node.js, node js, jest tutorial, jest testing, jest framework, nestjs testing, nestjs, nestjs tutorial, nestjs typeorm, typeorm, nestjs testing jest, nestjs testing tutorial, nestjs testing controller, nestjs testing module, nestjs testing services, supertest, testing api with jest, typescript, test driven development, tdd, nest jest, nest jest test
Id: dXOfOgFFKuY
Channel Id: undefined
Length: 44min 55sec (2695 seconds)
Published: Sat Apr 10 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.