4. Django REST Framework - Testing with Pytest + Fixtures

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] what's going on you guys and welcome back to the channel in the last video we created our crud endpoints that allows an authenticated user to create read update and delete a object using our api and we're using the example of a status or a status update much like with facebook or writing tweets so they can post it they can retrieve their list of tweets or status updates they can get back individual ones delete them and also update them now in this video we're going to wrap up this series by writing some tests as this is something that has been asked by a couple users and i think it's also very important to just have a glimpse of how one can go about writing django tests now we're not going to write tests for everything the sole purpose of this video is to write tests so we can test for our apis rather than our services that we've built however indirectly they will be covering them but we won't test like delete user status but rather the apis themselves so getting them deleting uh and posting etc the same goes with our user endpoints so to begin with the first thing is to make two necessary installations now i'm currently in the same directory as the manage.py file i'm just going to go a layer above so this one where i see the my readme but also the directory of uh of the project and the v venn file now i'm going to be using pi test for this django has its own kind of recommendation or kind of ships with django which is this test case i'm not a huge fan of that as writing tests using what the django test library isn't so pythonic and that's just in strong opinion held by me but also held by a few people so i'm going to use pi tests as yeah it just makes more sense writing it and it feels better when it comes to writing tests using pi test now i'm just going to go ahead and install what we need to install so the first one is pi test itself so to do that it's pip install and then pi test and then pi test django and that's just the extension to test django using pi test so hit enter okay let's clear that so to begin with the first thing is i'm just gonna do some a little bit of housekeeping and just delete these tests that we have inside our status app and our user app so just right click delete okay and the same for user and where is delete delete okay cool now the first thing that we need to do is create a pi test uh ini file and for that we need to do it inside our project directory itself so this api directory i'm going to right click new and then file i'm going to call this pi test if i can spell it dot i n i like so so it should look like that with the extension of i and i now inside that what we need to write is the following so in square brackets pi test underneath that we need to add in our django settings um to it so django underscore settings underscore module and this will be the path to it so core dot settings after that we need to add in the python files so the kind of files that you want pi test to actually test so we want um any file that has tests dot py test underscore and then asterisk so basically any file that goes with the example test user endpoints stuff like that and just put a dot py and you can also put this in as well tests dot py now we'll only be focusing on this one but just to give you an idea of other files that you can add in you can do tests you can do asterisks underscore tests you can name it however you want but the kind of custom is to have it so that the word test is in there somewhere but we'll be writing our test files in the format of test underscore and then whatever name you add okay so let's just close this now to actually see if pi test is installed properly um i'm just going to jump into the api directory so our project directory clear this and then run pi test and you should just get the following so there's nothing added but you know it's picking stuff up there's some warnings around config or whatever but this is fine for now it's working fine okay so the next thing is to add a directory and i'm gonna call this directory it's going to be a python package so create a directory and this will be called tests inside that in order for it to be a directory you need to add the init file so underscore underscore init underscore underscore so now we have the init file inside tests which makes it a direct a package inside that i'm going to add another directory and this is just more for keeping everything organized knowing where to look as yeah if you're building a rather complex api or writing something for production you'd probably you you could at least go along these lines so inside that i'm going to create a new python package and with pycharm you can do that just by right-clicking python package otherwise you can create an empty directory and just put an init file inside it i'm going to call this api this comes with this and make sure you have the init file inside that as well this one just auto created it for me and then inside api the last thing is um oh well not the last thing but the next file is going to be a file that will test our user endpoints so i'm going to call this test underscore user okay so based on our like organization we know that everything inside of tests api will be anything relating to api tests okay so that's enough of me talking let's actually write some code now the first one i want to test is our endpoint for registering a user now if you remember from a couple of videos ago we had this api here so register api which has the url register and just before that is prefixed with api so if you go inside core we have urls and then api here so api forward slash register and that is the endpoint i want to test as the first one so i'm going to import pi test then i'm going to say pi test if i can spell again mark dot django underscore db now this is a decorator as you can tell with the amp the at symbol here and what this is saying is that this test will be making use of the python of the django of the django database so next i'm going to say test register user and then put pass here for now so this is actually a valid test it tests nothing of course but it will it will pass so i'm just going to go back down into the command line clear everything and run pi test again and you should see here this line that i'm just i've just um highlighted that we have this little green dot and this green dot basically symbolizes a test which is this test here and it's got 100 pass rate which is true because it's at the end of the day it's doing nothing but let's actually allow it to do something now in order to actually get the um to actually make a kind of http request we need a client for that now in order to get this client we need to import it from rest framework so we can say from rest framework dot test import api client okay next i'm just gonna remove some of these lines next i'm going to create a object and call this api client and this will allow us to make post requests get requests you name it now inside back inside um now inside our test function let's go ahead and first create our payload so in this test we're expecting the user to register successfully so create payload this is going to be a dictionary what we need is the first name which we'll set to let's say harry and then last name potter oops lost i don't know where my head is today and then potter email harry hogwarts dot com and password let's just say time for some testing okay so that's our payload so this is what we'll send when registering now we'll say response equals client dot post and we want to target api forward slash api forward slash register forward slash and then here put payload so all this is doing is posting this data this dictionary to our register endpoint and then we get back our response and we store it in this variable now in order to get the data outside of response we can say data equals response dot data because remember when we post something when we post to a register when we register a user we get back the user with the id the first name last name and email we don't get back their password so we can say something like assert data square brackets first name equals and we can associate with this first name here so inside our payload say payload first name and let's actually run this so back in the command line i'm going to type in pi test hit enter and we get back 100 test coverage cool so so we know like that's working we're getting back the right response we can add some extra checks so we can say data and then last name payload last name and we also want to make sure that password is not in there so we can say assert password in the not in data let's run that let's clear all this again and also if you're using pycharm you should or may see this like green play button here that's to run a test pycharm does that for you which is quite nice i think i well i i'm pretty sure that there are some vs code extensions that you can use to kind of do this as well but pycharm just comes with this by default which you can click play and you get this whole like output you see that test passed but in order to keep it a bit more general i'm just going to use pi test in the command line and we have 100 coverage so we know password is not in the data that's what we expect then finally we can just say assert data square brackets email equals the payload and then email you can of course write the test as long as it's testing the right thing so we're testing to make sure the user registers correctly so make sure you do as much as you can to kind of cover what you can your style might be a bit different but with pi test we just have this simple assert equals um or certain not in sort of structure which is way more pythonic than what comes with the test case that or the django test the default django test library okay so let's just run this one more time pi test and we get 100 coverage okay that's nice let's go ahead and test some more endpoints so let's go ahead and try out test user login so we need this decorator again as we will be writing to our database then we'll say test login user and then after that what we want to do is first log the user in so let's go ahead and take this and then we can say that the next step is response equals client dot post and this will be targeting the login endpoint and the payload is going to be and instead of writing it in the variable i'm just going to put it in so we need an email and we'll say harry at hogwarts.com the password which is time for actually i'm just going to copy in case it fails time for some testing so what i did here is just i'm just logging the user in again on registering the user and then logging that user in and finally we can just say assert and then response and it comes with a status code the status code for login is 200 so we want it to be success a success and then finally let's just clear all this and run pi test again and we have two passes okay so that's nice while user is logging in we can also write another test to ensure that it fails if we give it the wrong credentials so let's go ahead and take this then we'll say test login user fail and one thing to note is that you have to put the test at the beginning of your function name otherwise pi test won't pick this up as a valid test as it will just be treated as a normal python function so make sure you always have tests at the beginning and then we'll just say that we'll give it we'll say response equals client.post we'll take this in again oops like that and the reason why i can actually use these values again so harry hogwarts.com and time for some testing is that when it comes to this test like the entire database is clean again so it this test is unaware of whatever we did in this test here which is quite nice i mean then you have to think about these things however just to make things clear i'm going to put bob and whatever here and then this will say response dot status code equals 403 and i think that's for unauthorized um and then let's go ahead and run it so back in the command line pi test and we get three fails so if we give it the invalid credentials we we get a 403 now one thing you may have realized is that this whole thing of registering a user again and then logging them in and then trying it is a bit tedious and can be quite annoying a bit cumbersome later on if you're testing the credit points or whatever you don't want to have to keep registering or logging registering the user logging them in and then running it and this is where pi test also becomes very useful in that you can create a file called a conf test and basically create objects that you can use or fixtures as pi test calls it to use in other tests now what i'm saying obviously may not make a lot of sense but we i can show you instead and maybe that makes things a bit clearer okay so before we continue with any more tests let's go ahead and inside our test directory let's create a new file and we'll call this and it has to be named like this conf test like so dot py so this thing here and inside that this is where we can add objects that we can use later on or fixtures and i'll show you so we can say import pi test and then from the actually we'll leave that for now we can actually add that later on but let's do pi test so ampersand oh at sign pi test and then fixture so we need this decorator then say user call it whatever you want this is just going to be the user fixture and then for this what i'm going to do is from the user i want to import the services and i'm going to call this as user services now i'm first going to create a user data class like this this all make sense in a second and then user data class and then i'm going to pass in like the first name which is harry last name which is potter email harry hogwarts dot com and the password which is i'll put i love magic i can't remember what i put for the in the other test but this is how you use data class and then let's create a user we'll say user equals user services dot um create user and then we'll just pass in our user data class as user data class cool and then finally what we can do is just return that user and remember this is actually returning a data class version of our user but that's fine for our tests you can of course use the model so you can just import the model and return the the object itself but this is just the user data class version and now now that we have this a fixture what we can do is go back into our test user file now inside there what we can add is inside let's say login user so instead of doing this whole thing we can have this user that we've already created like that and then as a result we can just remove all of this so we've already created a user exists in our model and we can actually go ahead and run it so we can say clear all this pi test and we get an error um we get a 403 and that's because why do we get a four ah because we have a incorrect password so we need that password this i love magic for this let's just clear this run it again and we get three passes so instead of like registering the user again which we really don't need to for testing our login we created a user fixture which is a yeah we did by default this user is added to our database and then we log that user in and this is exactly what we've done here we don't need it for this one because we're actually just testing registration we don't care about the user at this like before registering we care about it after in this case it doesn't really matter because we're just testing for fail so we don't need the user there but let's use it elsewhere so actually before we do that let's go back into conf test and if you remember we created this client equals api client instead what we can do is just create a client as a fixture so we'll say pi test fixture and we'll say this is called client and all that will return is just the client itself so we'll say return api client which we need to import first so we'll say from rest framework import or fromwest framework.test import api client and then down here we'll say client now what we can do is back in our test user we can remove this we can pass in client here and we this then just replaces it here um inside this we can add client and then here we can also add client okay so we can just run it again just to make sure that oops i open up something uh we can just make sure it's working so we can say pi test and everything's working fine okay let's go ahead and test our endpoint that retrieves a user so we'll say again we need to use this decorator here we'll say test underscore get underscore me and we need the user and we need the client okay so first of all what we need to do is log the user in so clients dot post and then we'll say api forward slash api and then log in and then payload well we don't say payload would say dictionary and then let's paste in this here so harriet hogwarts with the password i love magic let's paste that inside it and you'll you may actually be thinking like okay why are we logging the user in like can we just turn this into a fixture and we most certainly can and we'll do that in a second but just before we do let's go ahead and just finish this test so we log the user in then we'll say response equals client dot get and we'll say api forward slash me so this other endpoint that we wrote just to get back the user and you can first check to see that its response is status code is 200 let's just run this and it works so we're logging in correctly with our user fixture and then getting this endpoint then let's just say data equals response dot data and we'll say assert data id equals user.id assert data and let's just add in the email equals user.email let's run it again and everything's working fine okay so just before we continue let's just turn this thing here into a fixture as well so we already have a client and this is more of just a bland client there's nothing authenticated about it so let's go ahead and say pi test dot fixture and then we'll call this auth client so authenticators client for short this here needs our two fixtures so in order to use those two fixtures inside another fixture let me just shrink that so in order to use this client fixture and user fixture all we need to do is pass it in as an argument just like our tests and then we'll say client dot post and then forward slash api forward slash login forward slash and then put in our payload email and the email here is just user.email and then the password we need to enter that manually is i love magic and then we can just return client like so so this is just returning back a client that is authenticated um yeah so instead of having to log the user in each time so i'm back inside the test underscore user instead of client here i'm going to say auth client i'm going to remove this i'm going to move this oops not that and here i'm just going to say auth client like so and then i'm just going to clear this and then run it one more time oops and we have four passes okay so that's pretty much it for like um well we just have the log out i mean we can add that in anyway it's a quick one so let's just do that so we'll say pi test oops pi test dot mark.jango underscore db and what we need here is just an authenticated um endpoint or authenticated client so we'll say test log out and we'll say auth client and we'll say response equals auth client dot post as our logout is a post request say api forward slash log out forward slash there's no uh body for this post and then we'll say asset and then response dot status code is 200 assert response dot data and remember we have like a little message so say message equals and our message is so long farewell now let's run this so hopefully we should have five passing pi pi test and there we go 100 okay so that's our user endpoints i mean you could probably test this log out a bit more rigorously checking to see that like you know the user can't access i don't know this get me endpoint which i mean we could add but for the sake of time um this is our endpoint actually let me just get rid of this line our test for our user api okay so to kind of for the sake of completeness let's go ahead and write tests for our status endpoints so inside api new python file i'm going to call this test and status and this test.status py now for this let's go ahead and import pi test and say at pi test dot mark dot django underscore db the first thing is to create or test sorry test create status we'll use the auth client again because we need to be authenticated in order to create a status and the user so we'll say payload equals dictionary say content as that is the key and then the value for that let's just add something this is a really cool test i love tests log it's a nice little message and then we'll say response equals and then auth client dot post has this is a post request and then api status that's our endpoint and then we'll chuck in our payload okay so what do we expect from this well first let's get our data say response.data and we also want to make sure that a status actually exists inside our model now to do that let's just import from status import models and then down below we'll say status from db so what we're basically saying is that okay we're posting our payload to this endpoint as a result this this new status should exist inside our database to get that from our database we'll say models dot status dot objects dot all as there should only be one anyway so we can just do it like that you can filter it based on the user id if you want but i mean given that in this test case there should be no other status up until like this point and there should only be one afterwards so we can say that otherwise you can just say filter and then user id equals user dot id and then dot first but i'm just going to leave it at this it's a bit cleaner okay so here we'll say assert and then data content so the dates the response that we get back from this because remember once we post a new status update we will also get back that status update and here we can say status from db dot content let's go ahead and run this now with pi test if i type in pi test it will test everything that has test that is the name test underscore test underscore however if you want to single some things out you can say pi test and then tests and then api and then we want this file test status and that will just test that file there hit enter and you can see we get one if you want to test them all you can just do pi tests like that and now we have this test user dot py and also test stasis and this one here is working so we can just check the last thing or the next thing is data we want to make sure that the id matches up the id that we get back from the database to id and then we want to make sure that the same user that we use to create this is also used we'll say data user as it's a nested json dot id equals user.id and that would be enough to ensure that so and this is this user that we've passed in as our fixture okay so let's go ahead and run this i'm just going to clear all this i'm going to do pi test and i'm just going to test the individual file okay so that's working fine so that's creating our status let's cover the other endpoints so we'll do tests pi test dot mark dot django db and this will test the user status get user status so this will get back all the statuses that we've put up so client and then user and we'll say well let's just create a couple of them i mean we can create fixtures you can definitely go ahead and do that if you want but for now i'm just going to write them in here so model status and then objects dot create and i'm going to say user underscore id equals user dot id and remember we can't just do user equals user because the user in our fixture is actually a data class this doesn't accept a data class here it accepts only the django object of our user so i'm going to put user underscore id and then dot id will say the content is another test status and then let's create another one because this will return a list of our statuses and then instead of content for content here we'll put i love python python is magical stay in line with the harry potter theme um and then we'll say response equals auth client dot get and then forward slash api forward status and then let's just do a quick test and say that assert the response status code should be a 200 and then the length of it should be two because we've created two status objects so say assert len and then response dot data equals two let's run this test so i'm going to run the file itself and do oops pi tests actually just run it all and we got a fail because we get a 301 what is the reason for that so this 301 so this response status is coming back as this so the reason for that is because our api forward slash api for status it needs a forward slash at the end seems to be very picky about that but it makes sense we want it to be as exact as possible so pi tests and there we go we get our pass there no fails nice next we can of course test like the detail um for a four um we can get test the detail like view of it and you can definitely go ahead and do that but what i want to make sure is pi test dot mark.jango db so if a if we go to an end point where the status doesn't exist so we'll say define a function test function get user detail use a status detail i think that's better name 404 we will say auth client and here we'll just say response equals authclient.get and we'll pass in an invalid and point so status and then zero because there will never be a case where we create an object that um has an id of zero as django only allows for one or above then here we'll say assert that response dot status code equals 404 let's run that clear all this pi tests let's run the individual file and we get three passes so if we try to access this endpoint here zero we should and we must expect a 404 okay now you can of course just do the detail view itself so we retrieve it but i think given what we've already done hopefully that should be a bit straightforward but i'm more interested in the update and delete so let's go ahead and write those so pi test dot mark dot django db test and then we'll update user status and we need the auth client and we need the user so first let's create our status and we'll say status equals models dot status dot i'm just going to copy this one here and then let's create our payload for our put request equals dict and then content i just updated my status okay so um okay let's make our request so we'll say response equals client oauth client dot put and i think it would be best to name this instead of update let's call this put test put user status and we need our endpoint api status and we also need the um id of the states that we want to update so to do that i'm just going to put an f here format the string and put in status dot id forward slash and then we just need the payload next let's go ahead and check to see that we get the updated one so right now this status is staying the same we need to actually refresh this from the database and so to do that we can say status dot refresh from db and all that does is it collects it back from the database because if we don't do this this this variable here this stasis object will remain the same with the content of another test status that's why we refresh from db and then we can say the data actually i don't know why i'm adding all these unnecessary spaces but we'll say response.data and we say assert data id so the data that we get back from our endpoint from our request and then we'll say status dot id so the state the id must and should remain the same same we'll say that status dot content should equal now the content that we put up for our payload say payload and then content let's go ahead and run this i'm just going to clear the command line and then run it again and we get a pass so update is working correctly and then finally let's test our delete now we'll say pi test dot fixture dot django underscore db we'll say test delete user status this will be auth client and then user we need both these fixtures we'll say again we'll let's take in this status here let's copy that paste it in there let me just shrink this okay and then we'll say response equals both client dot delete and this again will be we need to format it so we'll say four slash api forward slash status forward slash and then the status id for those of you in case you missed it make sure you have this f there so we can format and add in our variable id here okay so now we just sent our deletion first we have to check to see if a response dot status code equals 204 so no content and this is 204 just to repeat and then we have to make sure that this status no longer exists inside our database now if we did status dot refresh from db this should actually fail so we can actually run this just clear all this and run so that our test should fail now and ah okay we need to fix this decorator so let's mark the django db okay let me just run that again sorry about that oops and we get back a few a fail and it's saying for where's the test yeah for delete for test delete user status it's failing you can see this kind of marker here it's saying this is where it's failing so status refresh from db now if we go down you'll see here that we get this status model status does not exist status matching query does not exist okay so it's actually to be honest technically it's working however this test is failing so in order to catch this error to make sure we get this error back we can use pi tests we could say with pi test dot raises and then models so we need this does not exist and that exists inside our status model so models dot status dot does not exist and inside that we put in refresh from db now what that's doing is it's saying that okay in this test when we run refresh from data from db we should expect this does not exist and this is a really handy thing that comes with pi test is really as you can see it's quite clean to write so it will expect this error it will expect this does not exist for whatever code we write inside this okay so let's actually just save this go back to our command line and run pi test test status and we get back all passing so that's really it with um testing our endpoints i mean we missed out one and to be honest for the sake of it i'm just gonna go ahead and add it in anyway because otherwise yeah it's just not it's just incomplete so we'll say pi test dot mark dot django underscore db and this is just testing to make sure we get back our detailed status and for that we need the auth client oops not in here test uh get user status detail both client and we need the user so let's go ahead and first let's create our status we can just copy this one here and then we'll say response equals auth client dot get and we need a formatted one so four slash api forward slash status forward slash and then status dot id forward slash and then we can say assert response dot status code equals 200. and we'll just get the data response dot data equals assert data and then id equals status dot id and let's just do set data content status dot content okay now that's actually all uh pretty much all our endpoints covered but yeah that's really it when it comes to writing pi tests for apis or our api endpoints what is missing is the service functions that we wrote and you can of course create that you can create a new python package inside our tests call it services and then run tests on each of the services that we wrote for the sake of this video i don't want to make it longer than it should be um it's pretty much the same way however with apis uh testing endpoints it's a there's a bit more kind of um there's a bit more involved as we need a client an authenticated client as well as just a normal client but that's really it all of this code will be available on the github link that's in the description i also have a discord channel so if you want to pop in say hi if you have any questions about any projects that you're working on feel free to do so and yeah that's uh really it i hope you found value in this video and also if you enjoyed it leave a like and subscribe but other than that until next time [Music]
Info
Channel: rithmic
Views: 14,648
Rating: undefined out of 5
Keywords: Django, django rest framework, python, Python, Tutorial, python tutorial, django tutorial, django rest framework tutorial, jwt authentication, authentication, permissions, API, Python API, Python Web Development, Web Development, Django Permissions, drf, CRUD, API Development, Python Development, Django Authentication, DRF Authentication, Django REST Framework Authentication, Python for Beginners, Python Project, Django Project, Pytest, Django Test, Fixtures
Id: 0OOS7sbVn4U
Channel Id: undefined
Length: 47min 46sec (2866 seconds)
Published: Tue Feb 01 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.