Flutter Unit Testing Tutorial For Beginners - Practical Guide

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
a unit test is a test that verifies the behavior of an isolated piece of code it can be used to test a single function method or class but what exactly does testing mean here there are two types of testing first one is the manual testing in this case you write code develop your app or software then you test the features and try to find bugs or defects in the app second one is automated testing automated testing is just a way to prevent the software from expected bugs but it doesn't mean the software does not have any bugs it just means the software does not have any expected bugs automated testing is generally preferred over manual testing why let's say you're building an e-commerce app as big as Amazon with manual testing you would need to rely on a team of testors to check each feature and component of your app and ensure that it meets the required specifications this process can be time consuming error prone and subject to human bias this on the other hand with automated testing you use automated tests to check each feature and component of the app and ensure that it functions as expected this method is faster more reliable and more efficient than manual testing as it can test a large number of scenarios in a short amount of time and can detect even the smallest defects in flutter we have three different types of automated testing first one is unit testing this is what we are going to dive deep into in today's episode second one is widget testing the second episode of testing series and third one is integration testing the third episode of this testing series the question now arises why do we need unit test there are many reasons for that first of all unit test helps us identify bugs at an early stage which means it can save a company lot of time as well as a lot of money second reason is when we want to refactor our code we might get a little scared because it might break certain features in our app when we have unit test and then we decide to refactor we have the confidence that if something goes wrong our unit test will tell us about that third reason is that debugging is very simple when we start unit testing because we can know what cases are failing and because of that we will get to know where exactly the bug lies to understand them even more let's just dive into unit testing and the first tab that we are going to unit test is a counter application this counter app is the exact same app that we get when we first create the flutter project we'll slowly work our way up towards more complex apps but first year we'll understand the basics of testing so let's analyze the app what have we got here we have got an increment button whenever we click over here the state of this counter changes so this is what we want to unit test but whenever we unit test there's one thing to keep in mind is the code testable if it's not testable you have to make it testable first this code is not testable why because our application logic which is this function increment counter then we are setting the state and incrementing the counter variable all of that is mixed with the user interface which is the home page since the UI and our application logic are mixed together we won't be able to unit test it it's pretty hard and we can just ease the process a little bit by separating the application logic and the user interface to do that we are going to go to the lib folder create a new file called counter dot dot and here create a class called counter and in here we are going to have all the application Logic for example I'm going to create a private variable of counter equal to 0 then I'm going to create a public getter account which will return this counter variable why have I done this this is because I don't want the value of this counter variable to change from outside of this class for example if I just go to main.dot let's import counter class all right I don't want this counter to be able to change the value of this count so I won't give access to it by making it private and then just put out a getter getter won't be able to change the value right it will only be able to give a value that's what I've done right now and now whenever we want to change any value we can do that from inside this class only because this private variable is accessible only inside this class so here I'm going to create a function called increment counter and here I'm just going to increment counter by one so counter plus plus now I can go to the main.dot file I'll create a final instance of this counter class I'll remove this counter variable there's no need for it to display the value I'm going to use this public getter also in the search stator I'm going to have counter dot increment counter and then I'll call such state so that the build function rebuilds and we see the UI change I'll scroll down and in the text I'll add your counter dot count to be displayed I'll save it and now if I see the application works just like before the only difference now is our code is separated in terms of concern for example my UI is different from my application logic this is good because now my counter class can be tested if we have this mixed together it will be very difficult to just test it out now let's get started with testing our app whenever we want to test something we have to go to the test folder here this is the folder flutter looks for when you start testing right you have to give a certain command so that flutter can initiate the testing process so this is the folder flutter goes into and here we already have a file called widget test and this test contains everything related to a widget test for this counter application so let's delete it for now we look at widget testing in the next episode The you a widget testing episode but for now let's concentrate on our unit tests so what do I want to unit test I want to unit test my application logic there's nothing to unit test in this file right we don't want to unit test my app it's just a UI UI class even my home page is simply now a UI class we have called certain functions from counter but all of our logic lives in this counter class so you want to unit test this counter class and that's what I'm going to test here so I'll create a file call it counter which is the name of my file underscore test dot dot this is the naming convention whenever you want to test something you need to add underscore test over here if you don't add it flutter won't get to know that this is a testing file and when you run the particular command to test a file it will go to this folder it will see that no this counter does not have underscore test so this is not a testable file so you always need to put underscore test it carries a special meaning and then click on enter the convention is always putting the file name first of the original file you are testing followed by underscore test but of course if you try to name something else for example if you just name this r underscore test and write test for this counter file it will work but when the application size increases it will get a lot tougher because other Engineers joining you in this app won't get to know what our test is that's why convention is to write the file name but underscore test is Convention as well as necessity okay so here we have countertest.dot now before starting the test we always want to make sure that in pubspect.aml file in the dev dependencies we have a package known as flutter test this package gives us a lot of functions and other utilities so that we can start testing so let's come to our counter test and let's get started so the first thing we need here is void main function whenever you have a DOT file obviously you need a main function which is basically an entrance to your DART program and here we are going to start testing now let's understand what we need to test first let's understand what we want to test there are multiple things we can test here first of all is whenever this class is instantiated the counter class is created for example over here when this counter class was created we want to check what is the counter variable so it should always be zero that's what we want to assure that whenever the counter class is created we want this variable which will be accessible through this count getter should be zero the second thing we want to test is that whenever this increment counter function is called the value of this count should be one so these are the two things we need to test so let's get started to test we need to call a method called test and this test does not have any meaning here but if you just import something known as the flutter underscore test package this test is now a function it does have its meaning and you see we get an autocomplete so you always need to make sure that you have flutter test package imported and then you can call the test function what are the two things that are required in this test function first one is the description so in your app you can have a lot of features right so you always need to give some description of what's happening and what should happen so the convention followed here is called given when then so let me write it down in the comment section so if you have given then then so we want to tell what's given to us so given to us so given to us is a counter class when it is instantiated then value of count should be 0. so we have followed this convention basically what is given to us given to us is a counter class right we only have access to the counter class and when it is instantiated so whenever the counter class is created then the value of count should be 0. this description can be really anything but it should always be descriptive maybe this isn't descriptive enough for you and your team maybe you want to try something else so you can try anything else which is descriptive then it's fine and after this we have a function so we have this function here so these are the two things that are required first one is the description this description will only be useful when we start testing our app when we click on this run function and this test runs this description will be helpful to us you'll see that when we run the app okay so for now let's focus on the function part what should the function here be basically how do we need to test we have given the description already so we know what to test right we want to create an instance of the counter and check if the count property on it has a value of 0. so here I'm just going to create final counter counter which is equal to counter and then I'm going to have final value equal to counter.com what I've done till here is basically created an instance of the counter class this is known as arranging right so I've arranged It Whatever things I need before starting the test I've arranged it then I want to act my ACT is basically when given is basically the counter class so I've arranged it which is given is basically the ACT so my Act was just to get the count value and then is basically a sort then is assert so here I just want to check if the value is actually zero to do that flutter test package gives us a function known as expect so here you just pass in the actual value so that is the this value what we get from act and then in the matcher you pass in zero so I'm expecting this value to be zero this is what this expect function means my counter dot count value should be 0. so I've passed in the real value that I want to check and this is the value that I'm guessing it should be right now just to test the app what I can do is go to the terminal come over here and type a command known as flutter test and then run it what it will do is go to the test folder and run all the files related to underscore test whatever files have underscore test in them and here you see it says all tests passed another way to test is come to over here on this tab in vs code click on this button here run tests and it will run all the tests for us we have only one test and if you click over here it expands and here is our description right given counter class when it is instantiated then value of count should be zero so this description helps when we have a lot of tests over here when our app size increases and we have lots of tests it will keep helping us because we know exactly what passed and whatever failed we'll go over there and understand why it failed and then fix our code so let's close it a little bit another way to run it is basically running this run function and we'll obviously see the output over here so I hope this much was clear now let's write another test just to make sure we understand it even better so here I'm going to write another test which is given counter class when it is incremented then the value of count should be 1. right because whenever there is 0 right you're at zero increment counter is called and the value should be 1 right yeah given counter class when it is incremented the value of count should be 1. so here what will we have let's see let's put some commas so that we have a formatting and here we are going to have our arrange act and assert basically like given when then in arrange I'm going to put my counter class because I don't have a counter class created here right nothing like it exists so I'll again I have to create an instance of counter class then we want to act so what is my ACT going to be well I want to call the increment function here it was just being instantiated so my arranging and acting was pretty much similar in my active error I'm going to have counter dot increment counter and then I'm going to get the value which is going to be counter dot count and then I'm going to assert by just having expect value to be 1. so I've created an instance of counter class I'm calling the increment counter method on it and then I'm expecting the value that comes from your to B1 now let's run this test so let's click over here let's come to our testing and here you can see even this class passed away so both of these tests are now working but there are a lot of things we can change over here for example as you can see these are two tests of the same class of the same category also so you want to group them right what we can do is basically just group together like mixed together in one thing for example the YouTube playlist itself if I have a state management tutorial I'm not just putting it like a separate tutorial I'm putting it in a playlist so that it's grouped together right so here I'm just having group and for the description I can just put counter class like this why have I put a hyphen here you'll understand when we run the tests again and now I can just take this test and put it inside of this group so these tests are now of One category which is the counter class now I can run the main function entirely so that I see something and here you see test counter test dot dot which is this file the counter class which is a group with a hyphen and then it says given counter class given counter class when it is incremented okay so now we have just grouped them together why did I want to group them I told you the reason but another reason could have been my counter to shift over here I don't want to keep creating the instance of this counter again and again right so I just put it in a group and now I can remove the instantiation from both of these tests and now if I save it and run it again both of these tests pass but let me show you another problem now if I go to counter and create a new function to decrement the counter my apps need to grow right so I'm adding a new feature so that a user can decrement the counter and here I'll have counter minus minus so I'm just decrementing the counter okay and in the UI you can make according changes but this is not a UI tutorial so I'm not going into it you can add it and call this method over here it doesn't really matter I just want to test this function now so I can create a new test over here I'll have test given counter class then it is decremented then the value of count should be and what should it be it should be minus 1 or 0 it should be -1 why because as I said unit test should be independent of each other for example if one day I decide to remove the increment feature only the decrement feature is available I'll remove this test as well okay and if it's not being incremented then the value will always be 0 right and then if we call decrement it will be -1 so it should always be -1 it should not be dependent on any other test so even if you remove the test this test shouldn't get affected they are independent of each other so let's bring back this test and here create a function so again we need to arrange act and assert I already have the arranged part my counter instance I want to act so I'll have counter dot decrement counter my final value is going to be counter dot count and I expect not X expect value to be minus 1. now if I run all the tests together let's see what happens so I'll go to testing here is my new class and you see there's no tick mark option over here because this part has not been tested yet so let's run all the tests again and see if it works no it doesn't as you can see are two tests the previous tests have passed but this test hasn't failed so this group test has failed and thus the entire counter test file has failed why did this test fail and here you can see the reason expected minus 1 actual is zero so what happened here let's analyze it from the top we created a group then we created an instance of this counter okay now we have this counter and we are using this counter to first check if the value is 0 it is indeed 0 so this test passed then we come down here we increment the counter and this is the same instance so the value changed from 0 to 1 so increment counter work and thus this test also passed and then when we came over here we decremented the counter the same counter was used right the same counter instantiation this class itself was used so the value of the counterwear is one because you have incremented it over here and when we decrement now it will be 1 minus 1 which will be 0. that means we are not really independent from this test because if we remove this test and save our file run our tests again now it passes so since we are using the same counter instantiation our value of count is also changing that means my test is dependent on other tests so what I need to do is basically put back my arrange in the same test and now if I run the tests again all three tests have passed because everyone has their own counter instantiation they have a new instantiation created because of which they do not depend on each other and now if I even remove this or even this test and run my function it still passes all right but now you might ask if I have a lot more function for example reset function created as well does it mean I need to create creating instance of this counter class that will be a little hectic right and of course if the app size increases and you have more complex functions it might become a little tougher to keep writing the code again and again so for that what we can do is go to the main function and your flutter test package gives us access to some functions which are set up set up all tear down tear down all these four functions there are various stages in testing process as well there's pretest which is set up and set up all there's post test which is tear down and tear down all and this is basically the testing so this is pre-test then testing and then post test what this means is these two functions run before test then testing occurs and then these two functions run now what's the difference between setup and setup all and tear down and tear down all setup is basically called before every test is called so when I use setup what happens is setup is called then this test is done again setup is called then this test is completed again setup is called this test is run and if there are many more tests again setup will be called and test will be done what setup all does is basically for setup all is called then test one is done test two is done and test 3 is done so setup is called before every test and setup all is called before all the tests let me write it down again setup is called before every test and set up all is called before all the tests okay so setup is called before every single test is run and setup all is like first it will run and then all the tests will follow to give you a diagram here it is setup then test then set up then test then set up then test how setup all works is set up all test test so I hope this clears the difference setup is called before every single test setup all is called before all the tests and similarly tear down is opposite of it tear down is called after every single test and tear down all is called after all the tests right so to give you a demonstration tear down works like this first test is called then tear down test is called Tear Down test is called Tear Down and in tear down all test test and then tear down all so to give you a demonstration let me copy this it's like this tear down is called after every test so let's take it from here and put it over here see we have test tear down test tear down test data and how this works is test test test and tear down all I hope the difference is clear now and that is why this is called pre-test this is run before the tests this is called the testing and this is called post testing because it's called after testing you can if you're having difficulty understanding it it's basically like in its state then our UI and then the dispose part similar to it so let's remove tear down and all of this and understand what we want to do we don't want to call setup all because that will be just like instantiating the counter class over here because it will run just once before all the tests have been executed setup is basically what we are looking for because we want a new counter class to be instantiated for us right so here we are just going to have late counter counter and then you're going to have setup create a function and then we'll have counter it is equal to counter we have to create an instance over here because this is basically a function right if I just put it inside of this none of the values will be accessible outside so if I just remove this counter does not exist and that's why I have to put this counter outside and now if I remove it it still works okay and this should be laid obviously if we don't put late we'll have to make this nullable and this is obviously not nullable so we are putting in late because we are sure this setup is going to run before every test and thus every test is going to have a new counter instance and thus the tests are not going to be dependent on each other let's remove all of the calls from here and let's save it now Also let's just remove tear down functions I think we have already moved cool now let's run all the test files and see if it works and as you can see all the tests have passed if I use setup all instead it shouldn't run so let's see if our test fails if our test fails we are actually successful so let's run the task and here you can see it again failed because it's behaving just like final counter counter which is equal to counter here right I hope it made sense and I hope it was understandable if not let me know in the comment section just to test on your own what you can do is create a reset function and create a test for the reset function itself that should give you more confidence so it's kind of like an exercise you should try it on your own and just so that you can confirm I'll do it on my own year so I have reset underscore counter is equal to zero then I'm just going to go over here create a new test description given counter class when it is reset then the value of count should be zero and then I'll again call the function here then I have counter dot reset and you see I don't have to create a new counter instance again because we have set up here also it should be set up not set up all we have set up here which will run before every test so we are sure we are going to get a brand new counter instance and now I can just expect value to be value should be counter dot count because I've not stored it in a variable I just wanted to show you can also put counter dot count over here and match it to zero because I'm expecting counter.com to be zero and if I go to testing run all the tests it should work for me and as you can see it works and just to see if the tests really fail what you can do is remove the decrement counter save it obviously there's an error over here and if you try to run it it tells you the errors exist but even then if you want to test maybe you can check if it works properly so in the decrement counter you're actually incrementing thus this test should fail let's see if it really fails so let's run and you see it really fails and it says expected was -1 actual is one so you are expecting minus 1 but actually the value was 1 because you incremented right from 0 the value went to one but you are thinking you have added the decrement counter so this is a bug in your app and to fix it you just go to the resulting function and add proper fixes then again run your tests and this time it works so this was the testing of the counter app this was a very simple and basic example now let's look at another example and that should make things clearer and we learn about something new now so this is the next app that we are going to test What's Happening Here is an HTTP request is being sent from the app and whatever data we get if it's a proper data then we are converting it to a user model that I've already created here and then I'm displaying it on the screen using a future Builder so I'm calling the methodia which is this exact method and displaying it on the screen I've already separated out the UI and the application logic as you can see I've already created user repository where I've created my function and then I'm just calling it in my UI file so both application and use UI is different now let's write a test for the user repository we don't have to write the test for UI as of now that will come under widget test what we want to do is write a test over here for this function and obviously other functions if we have them in this class for example if we want to create a function for deleting a user we'll add that to the user repository itself and we'll have to test every function present in the user repository because you want to check if that works properly so let's apply our unit testing logic now we'll go to the test folder and we need to create a new user repository test now a shortcut that you can take is instead of manually typing user repository test dot dot what you can do here is right click then click on go to tests and here you'll get a prompt if we would like to create a test file at test which is this folder followed by user repository test dot dot so I'm just going to click on Create and it automatically creates a file for me with boilerplate code but this boilerplate code is written for widget testing this is not a test for unit testing so we'll remove this and create a test function here so let's have test and what is our test going to be well it's going to be the user repository class because if our application size grows we are going to have more classes so first we need to group them so let's create one group so we have user Repository and here we'll write a test and the test first that we need to write is for the get user function so we need to check two conditions first we need to check if the response status code is actually 200 if it's actually 200 then we want to return a user but if it's not 200 we want to check if an exception is being thrown or not so you want to test two cases over here that's the most important thing you need to understand before you start testing what all conditions do you want to analyze first I want to see if the response is actually being fetched and if it is not actually being fetched if something went wrong so usually whenever an if condition comes in you have to check for both those conditions so what if the 200 status code is not there we have not United tested that right we have only unit tested for when the status code is 200 so we'll return a user model but what if status code is suppose 400 that means a bad request that time we have to check if an exception is being thrown or not so we need to test these two conditions so inside a group I'll create another group here buy another group because I need to test two conditions and those two conditions are related to just one function which is the get user function so I'm just going to type here get user function and all my tests are going to be written related to get user function inside this group so yes we can have nested groups in here we can have as many groups as we want we are allowed to have as many ever as we want so after having the get user function so I'll write test then I'll write given user repository class then get user function is called and status code is 200 then a user model should be thrown or return because Throne is for exception right we usually say this was returned and Throne is usually associated with the exception or something like that so return and then we'll just put this and here we have it so what have I written here well we are given a user repository class and when get user function is called and the status code is 200. our next test is related to our status code being something other than 200 so here we are also making sure that yeah the status code is 200 then only a user model should be returned so this is a test that you are going to do so let's convert this into a block and write the test for it so first of all let's create an instance of this user repository class and so that I don't have to write it in every single test what I'm going to do is use setup function so here we can have late user Repository then I'll have setup user repository is equal to user repository just like we saw in the previous example now we can take this user repository and then I'll do our arrange Act assert so in the arrange part what do you want to do we have did this we have done this right here we have created an instance of the user repository so nothing as of now for ACT we need to actually call the user repository class so let's call user repository dot get user and I'll have to store it in a variable so I'll have vinyl user is equal to await because this will return a future I'm expecting a user model so if I don't avoid this I'll get future user right because that's what the function is returning I'm expecting user so I'll just await it and to avoid it I'll have to make this function asynchronous and test function allows that and now after having the user instance I just want to assert that yeah this is indeed a user model so I'll have expect user and in the matchup what will I put in will I put in user not really because I really don't know what the email Name ID website can be right because this is being fetched from an external API what if this is being fetched from your database every user will have a different email password name Etc right so we can't manually put in some username although in this case we can because we know what the name username email and website is going to be but that's not always going to be the case so in these cases what you do is you check if it is a user model that is being written if it is indeed a user model then it will obviously have the right data because a logic over here is correct user dots from Json so here you can just have is a followed by user model written in the written in this generic type okay so now this is the syntax we are just checking if this user is a user model and user model is this class that we created so you can check if it's actually a user from this and it will check the return data types of both and confirm if it's actually a user model or not that is being returned but obviously in this case another thing you can do is just pass an user with the username email password things that you know and it should give you the right answer but in some cases you just can't have the username email Name ID password website whatever so in those cases you'll have to use is O and this is the Syntax for it so let's see if it's working let's run this function let's go to the testing tab and here we have it our test has passed just to verify again you can run your test again and it still passes that means this function is correct but there are two problems now if I want to test another function which is the status code of 400 let's say how will I test that so I have test written then I have given user repository class when get use of function is called and status code is not 200 because the status code can be 400 300 500 whatever right and we are treating all of them in the same way how are we treating that in the same way because in the user repository class we have written a status code is 200 then we return a user model but if it's anything other than 200 then we are throwing the exception so if the status code is 300 or 500 or 400 it really doesn't matter so here we are just typing and status code is not 200 then an exception should be thrown and if you want to be more descriptive and add what the exception should say you can just copy this text some error occurred exception should be thrown but I'm not going that descriptive this much is enough for me so let's have this test in a block function and now let's test out so we need a range we need act and we need assault so for arranging part we have already got the setup function for ACT part we are again going to have final user which is equal to await user repository dot get User make this function asynchronous take this user and you have to expect if the user is actually returning an exception so to do that what you can do is throws exception so this is one of the matchers that is given by the flutter test Library you have audio checking is basically if the user is throwing an exception but how does that make sense because it won't throw an exception the status code is indeed 200 right so if I just have this we'll pass it in you see we get an error because it's saying expected is instance of exception so here we are expecting that it throws an exception but actually we are getting a user and that's true because if this test passed that means you are actually getting a user if we are actually getting a user how can we get an exception right because this test passed so we are not getting an exception so this is incorrect and this is the problem here we are not able to control the behavior of this HTTP client we are not able to tell it that yeah now the response should be 200 now the response should be 400 now the response should be 500 we are not able to tell it so we don't have full access to it now let me show you another problem if I just go to my Wi-Fi tab I'll switch off my Wi-Fi and I'll run my tests all right so I'll run the test now go all the functions over here fail why is that the case it tells field host lookup it wasn't able to find this and that was due to no network connection obviously we don't have network connection how will this run so it's saying that yeah this test failed but actually this test hasn't failed it's just that there is no network connection and as I said before tests should always be written in isolation tests should not depend on any external factors like internet connection so we need to counter these two problems to counter these problems there are packages created so I'll tell you the two packages that are created for this first one is mojito and second one is mocktail both of them do the same task one of the difference between them is mojito works with code generation and mocktail works with us manually mocking the classes what does mocking the classes mean well basically let's take example of our user repository class our user repository class is being dependent on HTTP plugin right that is a dependency that means our user repository class or get user function is dependent on an external plugin that's not coming from flood up which is HTTP package right it's not really a flutter thing it's an HTTP package that can be found on pop.dev so a code is now dependent on HTTP and that's why it's an external dependency whenever we have external dependency we need to get control over them so that we can test them properly as you could see over here we didn't have control over HTTP and thus our tests have failed when the internet connection was gone so tests are not isolated so here what we are going to do is basically dependency injection what I'm going to do is take this HTTP plugin I'm going to import that from the Constructor of this user repository class of course you can do it from the parameter of this function as well but it wouldn't make sense when we have more functions for example if you have more functions suppose this is delete user this is update user and stuff when we have more functions we don't have to manually keep on getting HTTP client from this parameter and that's why we'll always need it from The Constructor of user repository it's just very easy for us so I'm going to take this HTTP plugin out and I'm going to inject it wherever needed now this won't make sense to you but wait until I'm done and I'm sure you'll understand so here we are going to have final HTTP dot client client then I take this from the user repository Constructor so we have this dot client now we can take this client and remove this HTTP plugin and I have client.get instead of http.client so now we are taking it from The Constructor and we are injecting the dependency where are we injecting it from we are injecting it from whatever classes we call for example let me save user repository I'll go to main.dot and it asks me that I need to pass in a client so here I'll have to pass in client and we'll pass it so you might ask what's the difference right I'm creating a client object here we are having the same thing in the user repository how does it make sense how it makes sense is it the user repository test now even your the user repository asks for client but you're not manually going to pass in client like this from HTTP package what we are going to do here is create a mock of this client by creating a mock we'll be able to change its Behavior the way we want it to so to create a mock first we need to add either of these dependencies mojito or mocktail if you're comfortable with code generation you can try out mojito or if you just want it very simple you can have mock day so for this tutorial I'm just going to go ahead with mocktail if you want mochito example I'll give it at the end of this section so let me copy mocktail and I'll do Ctrl shift p add Dev dependency not dependency make sure you need to add it as Dev dependency and then pass in mock day we need to add it as a depend Dev dependency not a dependency that's because dependency includes all the plugins or packages that might affect our application like HTTP for example if we do HTTP calls it might affect our app here but Dev dependencies includes things like linters so it's not really affecting the app it's affecting us as developers and makes our life much more easier that's what mocktail is doing here it's not really affecting the app it's affecting the test that we write so that's why we need to add mocktail to the dev dependencies here so now let's come back to the user repository test so let's create a mock client so to create a mock client we'll have to go outside of this main function and here we are going to create a class which will be called mock HTTP client we are not calling this mock client because mock client class already exists in HTTP package so you have more client it already exists in the HTTP plugin that's why we are calling the smoke HTTP client so that there's no confusion so let's create more of HTTP client then we'll implement it with the client Plugin or client class that's coming from HTTP plugin so we have just created a class and we are implementing all the methods all the properties inside this client class and this client class is an abstract class which has functions like head get post put and so on and on so we are implementing it but here's the problem we have to write implementation functions for all of these functions so we'll have to write implementations for 10 functions aware so if I just do this I'll have to write for every single one of them and then I'll be able to use this mocking class because it's a mocked class it behaves the way we want it to behave but that's just too much for us there's a lot of work to be put to get the smog done that's why we use packages like mock day they help us to mock the class by just extending it so if I just extend to mock here we don't have the error anymore so by extending the mock class what we have done is basically given all the properties that are required by implementing the client class okay I hope this was clear if not let me know in the comment section because it's quite important to understand this now let me take this mock HTTP client put it in the user repository and Master 10 like this it's just like the normal client class but now we have mock HTTP client and just so that we use this mock HTTP client over and over again what we can probably do here is just have late mock HTTP client mock HTTP client mock HTTP client equal to mock HTTP client so now we have instance over here and if you want to reuse this mock HTTP client we can reuse it anywhere now the behavior is with us but still if I run the test cases it will not work why will it not work because basically I'm just providing the client over here I've not really given it a behavior According to which it should run what that means is basically I've created my own class which is called mock HTTP client mock your is just making sure that I don't have to rewrite those functions again I haven't really set a behavior as to what should happen so here we need to do something known as stubbing in the arrange part so here we need to basically tell it that when something happens this should happen in our case what I want to do here is when a client dot get us called that means a get function is called I want to make sure that I send a response of myself which says this is the data and this is the status code so what I mean by this is let me type it out and then you'll understand it better here in the arrange I'm just saying when and then we have this notation we have a function here and we are just saying when mock HTTP client make sure to use that mock HTTP client not just client mock HTTP client dot get function is called and what is the URL well this exact thing so let's copy this and paste it over here so here I'm saying when mock HTTP client.get is called and when requires a function in the beginning you'll see this so I've passed in a function and when the get function is called and this is the URL then something should happen in that case dot then we want to answer something and what do we want to answer well we basically want to return a response saying this is the data and this is the status code so here we can have return response then we want a body and a status code so for the body let me pass in three strings and let me indent this properly the status code is 200 because here refuse the C the status code is 200. if you're not understanding no problem I'm going to explain it thoroughly but let me just type it out and here we'll just pass in the response which is this thing so let me copy and paste it over here I'll indent this properly as well and save it so we are getting an error now we are getting this error because the return type view response isn't a future response so when we call the get method what we get is a future response not really just a response so to make this future response you can either add future dot delayed so you need to wrap this with the future dot delayed and pass in the duration or you can just pass in async to this then answer function after you do that the error is now gone so let's understand what we did well we are just making sure that the behavior is according to what we want earlier we had just mocked the class but here using that mock client we are passing it to this when function which basically Alters the behavior of our plugin here we are just making sure that we say that when a get function is called using mock HTTP client and that will be the case when this function is run because that's what we have passed through this Constructor right here if you see a user repository class is created then we are passing in the mock HTTP client and that mock HTTP client is being used to call a get function so yeah we are telling when mock HTTP client dot get is called and this is the URL then we want to answer with this response so we are no longer dependent on the data this URI gives us okay we're not at all dependent on what this is we are only dependent on what we return here so whenever this get request is called then we want to answer with this response now we can cut short the data obviously because we don't need address we don't need phone we don't need company all this is enough so we are returning a response then this is our response data and this is the status code that you are passing in 200 earlier we weren't able to pass in the status code right now we are able to pass it in because we are totally in power of what can happen and what cannot happen earlier this wasn't in our hand now it is in our hand so if we want we can pass in 200 and this thing will help us over here as well because here the status code is not 200 I can pass in 400 500 or anything else so now we have successfully determined the behavior so we are just telling when this function is called then answer this and then we are calling the get user function from user repository so what happens here is this get user function is called client.get is called that client is being strictly mock HTTP client because that's what we have passed to the user repository class using dependency injection then we are having this URI and then we are checking if response.status code is 200 and we have also passed in that this is going to be the response we've returned a response right here so we have passed in that the response has a status code of 200 so if status code is 200 it will return a user and we are expecting a user so let's try to run this case and see if it works so let's call run function and here it's saying there's an error and obviously the error is in over here this is not a type of Json so when we call user Dot from Json aware it's not really able to identify so to make the suggestion you'll have to remove this comma save it now run this test and you see everything is tick mark here the test has now successfully been run but this test is still not working so if you run it it throws an error right so to make this run again we'll have to mock it so let's mock the behavior or stop the behavior not mock mocking is this part stubbing is basically changing the behavior so again we'll have when and then we'll have when mock HTTP client dot get and this is a URL so let me just copy this pass it in when mock HTTP client dot get a score then we need to answer with this and what do we need to answer with well a response right because that's what mock HTTP client dot get is requested if I try to return something like a string it won't be able to accept because the return type string isn't a future response because you're basically stubbing the behavior we are altering the behavior but by altering the behavior we are not changing the return data type of this get function the return data type has to be the same thing so whenever this get function is called we'll have to return a future response so to make this a future response you can add future dot delayed or just make this function async due to which this becomes a future response and then you'll pass in response the body is an empty function because there's an error now and the error is supposed 500 which is internal server error so we'll just return that and a shorter syntax to do this might just be an arrow function so you can add this then the return function return keyword is not needed and here we have it so when mock HTTP client dot get is called then we need to answer with this and now we're hoping it should run so let's run the entire testing scenario I'll click over here why is the error occurring now well that's because we have awaited this right so we have stopped the behavior that part is cool now we are acting and when we are acting we are awaiting it if we await it it will actually run the function and it will actually through an exception so to resolve this we might have to do try and catch block over here but that's not exactly what we are going for we don't really want to catch exception and do anything with it so yeah what I'm going to do is away remove the await and then it should work so let me run it again and returns successfully by the why did this solve the problem well because if you call user repository dot get user it returns future of the data type user to us thus it runs the entire function and when it sees that the status code is not 200 it's 500 it throws an exception if it throws an exception this function is like oh my God we got an exception and thus we need to I'll try catch block so that we handle that exception but we don't want to do anything if we have an exception we just basically want to expect that it actually throws an exception so if we are expecting that we remove a weight because we are not interested in the return data type of the user we're just interested in if we are getting an exception or we are not getting an exception so we'll remove this this is of the type future user and then we are expecting it will throw an exception and now just to test it I'm going to stop my Wi-Fi because I want to test if my testing logic is in isolation or not so I'm just going to run it again and you see it still works perfectly now our cases our test cases are really in isolation so if you have understood it till now you can skip this part if you've not understood then I'm just going to give you give you a brief demo again and I'll tell you the entire dry run of this so basically we have user repository created then we have the user repository then we are using user repository.getuser then we are expecting it and the same thing is being done over here but then we realize these test cases are not in isolation they are not in isolation because our code your ad user repository is dependent on an external factor which is called like an external dependency so what we are doing to remove that external dependency is injecting it which is called dependency injection we're basically injecting the thing the external dependency from The Constructor we can also do it from the function but Constructor is just making it easier if you have more functions over here in this class then we are taking this client that we are accepting from The Constructor and using that instead of just http.get then we resolve the error in the main dot dot file then we are going to the user repository test and we are creating a mock out of it to create a mock we just Implement client but then we realize that we'll have to write 10 more functions for it so we just add extends mock to it and when we add extends mock which is coming from mocktail package it all we already implements all the functions that we need so we don't have to write 10 function blocks then we are using that mock HTTP client and creating an instance of it the same instance is being passed to the user repository class and the same instance is being used in the arrange part so that we can stop the behavior stop the behavior is basically altering the behavior of the HTTP client where we are saying whenever mock HTTP client dot gets called then we need to answer with this just ignore anything else you'll have to follow what I say in this then answer function that's what it does it just says whatever data you get from this I don't care about that this should be the response and based on that response I'm also able to pass a status code and then check if it's actually a user then in the second case we are again doing that we are telling when mock HTTP client dot get function is called then you need to answer with the response where we won't get any response because there's an internal server error and the status code is 500 so we are altering the behavior here and then we are just saying that when we call get user function it should throw an exception we are not using a weightier and here we are expecting that whatever the user repository.getuser does it will throw an exception and thus our code is now isolated and we are able to test our functions even without connection so I hope you understood this this is the most difficult concept people find in unit testing so you can try and play around with it I'll mention this code in the GitHub repository below I would recommend you to implement more apis for example getting articles getting to-do list whatever is present in the Json placeholder API and just play around with it yourself so this is it for this video thank you so much for watching and I'll see you in the next episode of testing series which is widget testing
Info
Channel: Rivaan Ranawat
Views: 31,672
Rating: undefined out of 5
Keywords: flutter, flutter 3.8, flutter unit test, unit testing, flutter unit testing, flutter automated testing, testing, flutter_test, flutter mocking, flutter mock, flutter stubbing, flutter test, flutter automated test, automated test, flutter unit test for beginners, flutter for beginners, flutter beginners, flutter 4, flutter 3, flutter practical guide, flutter testing, flutter testing tutorial, flutter tutorial, flutter unit test tutorial, flutter unit test guide, flutter guide
Id: mxTW020pyuc
Channel Id: undefined
Length: 66min 53sec (4013 seconds)
Published: Wed Mar 08 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.