Testing ViewModels - Testing in Android - Part 10

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey guys welcome back to new video so as promised in this video we will test our shopping view model so you will learn how to do that let's make sure that you are inside of your shopping remodel class right click on the class name click on generate and then we click on test so android studio will generate that test class for us we won't select groovy junit here we will instead use junit 4. the class name is fine and we just press ok and well in which search set we actually want to create that test case we will do that in the test folder because a view model is just a plain class that is not an android component and for that we can simply run those test cases on the jvm which which we should always do if we can do that so we select test here press ok and you can see android studio generates us that test class alright so what will we actually have in this test class here of course we want to test our actual shopping view model so what we of course need is an instance of that so we create a private latent var view model which is of type shoppingviewmodel and inside of the before function so the function that is executed before each of our test cases the setup function we want to initialize that viewmodel so viewmodel is equal to new shopping view model and now you can see we need a shopping repository and now it also should get clear why we created that fake shopping repository because in this test class here we will need that right now because here we don't want to create a default shopping repository because for that we need a shopping dow and a pixabay api which we don't want to use in our tests because making api requests or database calls just takes time and unit tests should run fast so we definitely don't want that what we will instead do is we will simply use our fake shopping repository here and instantiate that you can see that doesn't take that node dial and pixabay api so if you remember that fake shopping repository just imitates the behavior of our real repository but in a way that is very efficient for tests because that just uses a plain um list data structure and not a real database so that is much more efficient and perfectly suited for our tests here then we can write our actual first test case in here so add test function and let's actually first test if we want to insert an item into our database and we'll leave one field empty so that of course should return an error and i will use that backtick type of writing again because we're inside of our test folder and not our android test folder where that isn't possible sadly but here we can use that so we can just describe what our test case tests and what we expect it to return so insert shopping item with empty field returns error so that is our function name and in here we can write the test case so first of all we of course want to use our viewmodel and insert a new shopping item here so we use our insert shopping item function here not that shopping item into db function we just want to check if the validation of the input is correctly here so viewmodel.insert shopping item choose any name let's say we'll leave the amount string empty here because as i said we want to test if we insert a shopping item with an empty field we test if that really returns an error here so we of course also need to provide an item with an empty field and for the price we can put anything here that doesn't really matter then we want to get the value of the resource we emit in our view model so that function actually emits such a resource class so either loading success or error depending on if that input was valid or not and we want to get the result of that so val value is equal to view model dot insert shopping item status that is the live data in which we will post that status later on dot get or await value and you might remember that function we used that before but right now it doesn't recognize that because we are instead of our test source set and we created that extension function in our android test sources and those are not really compatible with each other so if we um credit class in our android test source set then we can't use that in our test source it so what we have to do is we need to just copy that class over here to the test source set let's open our android test source set here take our live data util android test copy that and in our test source set we simply paste that and we move the android here so just live did a util test and then also rename this to get away value test and also down here and then we can go back to our shopping view model test and also append a test here and then we can import that and we don't get any error and now we want to assert that this value here so if we press ctrl q on that you can see that returns an event of type resource of type shopping item and now we want to assert that this resource here is actually an error resource so if that is an error resource this test passes because if we want to insert a shopping item with an empty field we expect it to return that error resource so what we will do is we will write assert that and we don't want j units assert that function so what we will do is we will remove this junit assert import here delete this and re-import this function pressing out plus enter and this time we will use that assert that function from the truth library and then we can simply pass our value dot get content if not handled so the first time we call this function this will actually just return the content so our resource here and that is the first time we call that here and we make a null check and use dot status and then we can simply check if that is equal to status dot error okay that's already it for our first test function let's create another one make a little space here and we can actually just copy this test block and just make some adjustments on that i will call this insert shopping item with too long name so let's say we enter a name that is that has more characters than our max name length we created in the last video so in our constants file we have that max name length which we set to 20 characters and now we want to test if we enter a name with let's say 21 characters if it really returns an error in that case so we go back to our shopping view model test and this time we want to insert a shopping item with with an valid amount so the amount let's say five but the name should be of length longer longer than 20 and because we don't want to hard code this at 20 here instead we want to make this dependent on our constant because if we change the constant we shouldn't also change the test case this should always be dependent on basically the constant we choose for the max length so what i will do here is i will actually create a string with a string builder that is just one character longer than our max name length constant is so val string is equal to build string which is a cool kotlin extension on our extension and kolkata another function in which we can just um have an instance of a string builder and in here we can create a for loop for i in one to constants dot max name length plus one so we actually just make sure that we add one more character as our max name length is so we can simply use append to append a new character to that string builder and it doesn't really matter what we choose here i will just choose a one and then we can use that string and pass it for our name so the advantage of this is that even if we now change that max name length constant let's say to 30 then this same method will construct a string with 31 characters so that will still be a valid test case but if we hard code this let's say we would write a string in here with 20 characters and then we increase the max name length constant and then this test case will fail even though it shouldn't fail and then we also need to change the test case again which we don't want so we really want to make sure this is dependent on that constant here okay and then next i want to have a very similar function for our max price length so we actually have another constant here for our max price length which is 10 characters and i also want to test the same here that if we enter a price that is too long and that it should return an error so we paste that again insert shopping item with too long price this time we also want to construct the string here this time with max price length and for the name we just choose name again and for the price this time we will use that string and then what can we test next we can actually test if we enter an invalid amount so the actual reason we don't have such a constant here for the amount is because the amount is an integer and integers behave different than floats so that price is actually a float and if we enter two high number four float then it will simply display infinity but for integers if we overflow that integer basically then it will crash i think so for that we need to use some other logic we don't want this string builder here instead that is simpler actually we just enter a name for the name here and for the amount we just enter a very large number where no way that can be saved in a single integer variable and the name of that function will be insert shopping item with two high um amount and that should also return an error and let's add one last test case for our insert shopping item function which is of course the test case that should succeed so if we enter a valid item then it should return a success status in the end so let's copy that again one last time paste that below we will choose a valid amount a valid price a valid name and actually also change the function signature here insert shopping item with valid input and that should return success of course so first we insert that item with valid inputs then we wait for the status to emit a value and then we simply want to assert that this value so the status of that resource is actually equal to status dot success this time okay so if we now actually run our test cases then we get an error that the command line is too long short command line for shopping view model test or also other android junit default configuration let's click on default here then this window will open up and here we can basically choose something for shorten the command line in which i will just choose jar manifest here so that is just an option we need here so that this error goes away we press on ok and if we now rerun our test cases then you can see now that works our test cases all fail that is what we expected because we don't have the implementation yet but also something important we need to consider here is that the same way as we did it in our android test where we test our dao our database functions we also must use that instant task executor rule so we implement a junit rule here with add get colon rule called instant task executor rule and set that to new instant task executor rule so that will just make sure that um everything will run in the same thread one action after the other so if we now rerun our test cases then wait a moment and you can see now something works differently all the test cases basically were um wait until the live datas emit a value but since they don't you can see live data value was never set they just all time out because we don't post anything to the live data yet since we don't have the function implementation in our actual view model yet but the error from before is gone because we implemented that instant test executor rule here so let's actually change that and according to tdd principles let's now implement the actual function because now we have the test cases that test the function and all fail so now we implement the function in a way that these test cases shouldn't fail let's go to our shopping view model and also minimize that and start with our insert shopping item function first of all let's make sure that we post an error resource if any of these fields are empty so if name dot is empty or amount string that is empty or price string dot is empty if one of these is the case then we simply want to use underscore insert shopping item status that post value event of type resource dot error oops um control p is what i wanted to do and the message for that is the fields must not be empty and for the data we simply pass null here and then we can just return out of this function okay so next if our name dot length if that is greater than our constants dot max name length and then we also want to emit an error resource so underscore insert shopping item status dot post value event of type resource dot error with a message the name of the item must not exceed uh constants dot max name length characters and again pass null for the data because we don't have any yet and also we turn out of this function then next let's actually copy that paste it below and do the same for the price so if price string dot length is greater than constants.max price length and then we want to post an error resource the name instead of that we use the price of the item must not exceed max price length characters and also return out of this and what is missing we also need to worry about the amount so we just create a val amount which will be the integer amount and we set that equal to try um amount string that to integer and if that fails so if we catch an exception e is an exception but i want the kotlin exception here and then instead we will post a new value in underscore insert shopping item status so that post value event resource that error with a message please enter a valid amount and pass null and don't forget to return here and now we can actually construct our actual shopping item so well shopping item is equal to a new shopping item first of all the name will be just our name the amount will be our amount the price will be our price string.2 float the image url will be whatever is in our current image ul dot value and if that is equal to null we will simply use an empty string here and then we can use that item to insert it into our database insert shopping item into db pass our shopping item here and i also want to use set current image ul and reset that basically to an empty url because if we insert a new shopping item into our database then we will directly pop the backstack so we navigate to our shopping fragment so where all the shopping items are displayed and if we go back to add a new item then we want to show an empty image basically so we don't want to show the same image as we did before so that is why i will clear that current image url and by the way you can take it as a homework to also test this functionality so if we insert a shopping item with valid input that you test that this image url is empty afterwards so take that as a homework and write the test case at home and in the end we just want to use our underscore not image underscore insert shopping item status again and post the value event resource dot success this time because if we reach this point everything was successful and we can attach our data which is our actual shopping item here so let's actually also implement this search for image function which will be much lighter than our insert shopping item function so we check if our image query is empty in that case we just want to return out of this function we don't want to emit an error resource here because it could be that the user just um deleted his search string so he just we erased all characters and then we don't want to show a snack bar or something like that because that is not really an error um and then if this is not empty we want to use our images live data and set the value to a loading resource so event resource dot loading and repairs null for the data so if you wonder why i use images.value here and not post value the difference is that that value if we just set the value here then the change will always notify all of our observers of that live data if we use post value and if we would use post value several times in a very short time frame then only the last time we'll notify our observers and for our test cases later on it will be very important that we choose that value here because we need to make sure that our observers actually always get this loading resource and then get the success resource or the the error resource so that we can just test that properly so in actual production in a real app this is not noticeable if we use dot value or post value here but for our test cases that is indeed very important that we also get this loading resource here and not directly the success resource so that is why i will use that dot value here instead of post value anyways afterwards we want to launch a crew team because we want to make our api request so viewmodelscope.launch and in here we will get the response by writing val response is equal to repository dot search for image and repairs our image query and then afterwards we can simply use our underscore images live data again and set the value of that to event of response because our response is in the end just a resource and we just want to wrap an event class around that so now we implemented our view model let's go back to our shopping view model test and re-run our tests and you will see that they will fail again i will explain why so let's wait a little moment here and why okay we have two exception imports here let's remove one and run again so run our test cases again wait a little moment until gradle does its job and you can see we get an error here even though the test case is all passed we get an exception in thread main module with the main dispatcher had failed to initialize okay so the reason why this failed is if we take a look in our shopping view model you can see we actually use a crew routine inside of this insert shopping item function because this calls our insert shopping item into db function and here you can see that is a suspend call and that uses the main dispatcher which we don't have in tests because the creatine main dispatcher relies on the main looper which is only available in a real app scenario basically so if we launch a real app but since we are inside of our test set here where we run our tests on the jvm we don't have that real app environment so we also don't have access to that main dispatcher if we would have our tests inside of the android test directory then this wouldn't be a problem because then we test our tests on an actual android device but here we don't so to solve that problem we need to define our own junit rule and sorry this video is getting very long but this is really the last thing we will do in this video to create that rule so we will go to our test source set here and inside of that package we will create a new carton follow class which will be called main curotene rule so we will just use a special dispatcher which is not the main dispatcher and then we can just use that rule in all of our projects in all of our test classes where we need that where we actually use curoutines inside of the test source set and that won't be any problem anymore so let's actually minimize that here first of all we annotate that with experimental crew teams api and in the constructor of that we can pass the dispatcher for that rule so private val this patcher is accrued in dispatcher and we set that by default to the so-called test crew routine dispatcher which is exactly suited for these test cases and the test source set as i said and that will inherit from test watcher which will so so the test watcher implements the test rule interface i think and that is what makes this class an actual rule for junit tests but we also want to inherit from test curotene scope and we use by test protein scope and we pass our dispatcher here so inside of this class we on the one hand want to override the starting function so when we actually start a routine with this dispatcher here with this rule actually if this rule is active and we start a query in there then we want to use dispatchers dot set main so we can choose a different dispatcher for the main dispatcher and for that we will simply use our test dispatcher we pass as a parameter and if there is a starting function then there's also finishing finished function in which we want to first of all clean up test curtains so that all these finish and afterwards we call dispatchers dot reset main so that will just undo what we did here so when we start a crew team we replace the main dispatcher with our test dispatcher and when that croutin is done we will just set the main dispatcher to the real main dispatcher again by calling reset main and if we now go to our shopping view model um actually the test function a test class here and implement that rule also add get colon rule var main curating rule which is a main query rule and you can see we also get a warning here because we need this annotation experimental curtains api but now we actually have that rule and that applies to all of our test cases here in this class so if we now rerun our tests again and then hopefully we shouldn't get an error at this time and as you can see all tests pass we don't get an error everything is working perfectly fine so if you followed through this video until now then congratulations now you know how to test view models that is very important and i also got a little homework for you which i also mentioned already in the shopping view model there is this insert shopping item function and as i said you can just test if this really sets the current image url to an empty string after we successfully inserted an item and you could also test this set image url function here so just tests if you call this function with any url that this url can be observed from this live data afterwards so take that as a homework and you can practice with that so please if you like this video hit the like button if you love this video give me a comment below and if you are not a subscriber of my channel then just subscribe to it you will get regular android content every second day have a really nice day see you next video bye bye
Info
Channel: Philipp Lackner
Views: 16,978
Rating: 4.9746032 out of 5
Keywords: testing, android, test, test case, junit, mockito, mock, fake, stub, tdd, test driven development, intellij, android studio, unit test, unit testing, integration test, integrated test, end to end test, ui test, kotlin, mvvm, live data, coroutines, dagger, retrofit, room, database, roboelectric, android test, jvm, flaky test, development, programming, git
Id: B-dJTFeOAqw
Channel Id: undefined
Length: 28min 3sec (1683 seconds)
Published: Tue Sep 01 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.