Creating a Fake Repository for Testing - Testing on Android - Part 8

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey guys welcome back to a new video in this video we are going to set up our repository so as you probably know if you're watching this series in the repository we just um we just have all of our data sources coming together so on the one hand our local data source our roon database and on the other hand our remote data source in this case our pixabay api but if you have seen the video about how to write good tests and hopefully you have because that is super important that video and then you know that one factor of a good test was speed and network calls are everything but fast so we actually don't want to make actual network calls in our test cases instead we need to think of another solution for that and that is where so-called test doubles come into play so test double is basically just another version of the class so in our case our repository that is created for testing so that is just very well suited for test cases and that means we will have two repositories here in our project on the one on the one hand our real repository which will just get the data from our room database and also make the actual api call so we will just use that repository in our real app of course because we need the data from the api there but on the other hand in our test directory in our test source set we will have a different version of that repository but let's actually first start to implement a real repository here and the rest will get clear when we get to that point for that we actually first need to include a so-called resource class i think you probably know that if you're watching this series here that is just a class that you can use in every of your projects that is perfectly suited for error handling loading handling and success state handling so for that i will go inside of our other package create a new kotlin follow class just a plain cotton file called resource and here i will just paste some code i copied from my project as usual you can get this code from the link to my github in this video's description and sorry usually i'm not a fan of copy and pasting in tutorials but this series should really focus on testing and not on any best practice styles in mvvm or architecture stuff because for that i really have my other playlists you can watch for that and you should really watch them before watching this series anyways if you still don't know what this class does it's basically just a generic class here you can see that takes a generic perimeter t and in the constructor it has a status a data parameter and a message parameter and we have an enum class as well here that can either be a success error or loading status and we can just use this class to wrap wrap it around any kind of object here and then easily check if an error occurred if the object is currently in the loading status if it was successful and then we can just later on really well observe on on those statuses basically and also get the data of that object from this data parameter here all right so let's actually create our real repository here inside of a new package so create a new package in our root package called repositories and in here we are going to create a new cotton follow class select class here and call that default shopping repository then press enter here and we will inject the constructor with dagger hilt so add inject um import that constructor and in here we will have a private val for our shopping dao which is of type shopping though of course and also private val for our shopping or actually it's called pixabay api of type pixabay api okay so far so good but if we now would just implement these normal functions here in our repository for example to search for something in our api then that would be a pretty bad practice because as i said we will have a test double so a second implementation of the same repository and that will also implement the same functions we have in this repository so what we will instead do is we will define an interface in which we define the functions our repository should have and then we let all of our repositories so on one hand this default shopping repository and on the other hand the repository will create for testing both of these will simply implement that interface so i will go to our repositories package here and create a new code and follow class called shopping repository and that will now be an interface and instead of this interface we will now define the functions our repositories should have so on the one hand that will be a function to insert a shopping item into our database so suspend function insert shopping item which takes that shopping item as a parameter and we will have a function to delete a shopping item so also suspend function delete shopping item and pass the shopping item we want to delete here then we will have function to observe all shopping items which will return a live data of type list of shopping item we will have a function to observe the total price which will also return a live data by the sum of type float and finally for our api we will have a suspend function search for image which will take the image query as a parameter which is a string so basically the search string and that will return a response of type image response so that is now our interface shopping repository and then we can simply implement that interface in our default shopping repository and also in our repository that we create for testing and both these repositories will then always implement the same functions so we can go back to our default shopping repository here implement that interface here so shopping repository and then we can simply press ctrl i to implement all those functions press enter here and there we go so remove all those to do statements the most of these functions will actually be pretty empty actually only a single line so in insert shopping item we will use our shopping shopping come on shopping dow the dot insert shopping item and pass the shopping item we pass as a parameter and then we will also have a function here to delete an item so shopping dow dot delete shopping item pass this item again here we will return um shopping dow dot observe all shopping items here we will return shopping down dot observe total price and in our search for image function we want to write a little bit more because we want to do some error handling here and by the way this must not be response here this must be resource so the class we actually um paste it before so we will actually do some error handling in this search for image function and then depending on if we have an error if that was successful we will return a different type of resource here if we change that here we of course also need to change that in our shopping repository interface so just change this to resource here and then we can go back in here so in here we will simply return try and in the try block actually let's write the catch block first so we catch an exception and in case we catch an exception we simply want to return a resource dot error with the message couldn't reach the server check your internet connection and we don't have any data we can attach to that so we simply pass null for the data okay and then for our try block here in this one we actually want to make the actual request to our pixabay api so val responds is equal to pixabay api dot search for image and we pass our image query and then we can check if that response was successful if it was we want to check if response actually has a body so we sponsored body question mark.let so we make a null check here if that is not equal to null that means everything was successful then we can simply write return at let um resource.success here and we simply pass it for the data of our success resource so later on we can then extract that image response here out of that success response resource and if that was null then we simply want to return resource.error and in that case we didn't know which error occurred so we just write an unknown error occurred and we don't attach any data and then in case the response was not successful so in the else block we also simply want to return an error resource with an unknown error occurred and also don't pass anything for the data and now it's actually time to create our test double for that repository so let's move to our test source set here not android test we will do that in the test source set because our view model doesn't actually require any android components or something like that so we can simply do it in this folder and in our root folder here we will create a new package called repositories and as i said in the last video or the second class we should always have the same package structure as we have in our main package here so that's why i named this package exactly the same as this package and in here we will right click and create a new kotlin follow class select class here and that will be called fake shopping repository so when it comes to test doubles then there are actually several types of those test doubles very popular are so called fakes and mocks so in this case we have a fake test double and effect test double is basically just a version of a class that is very well suited for test cases but not very well suited for production and that is exactly what we want to have here so we want to actually test our repository in our api but we don't want to make the actual network requests which would be suited badly for test cases so in the end what we will do here is we will just have a simple list that simulates our actual database and we will have a function in which we can just set a boolean if we want to return an error from the api or not so we can actually set up our repository as we want and then check if it actually does what it should do and if this confuses you remember we don't write this fake shopping repository class here to test our actual repository instead we write it to test our viewmodel later on so actually we only need a repository that we can pass to our viewmodel constructor in our test cases and since we don't want to create such a default shopping repository in our test cases we need to create a fake version of that that basically just simulates the behavior of our real repository but not in the same way as our real repository so as i said we will just have a simple list as database and that is fine if we use that for a single test case only and then recreate that repository because it doesn't matter for us if we use a list for the database or real database because the behavior of both are the same if we use them for a single test case only so inside of this fake shopping repository we will actually first have a list of shopping items so that is called shopping items and that can actually be a private val here and we're going to set that to immutable list of shopping item then we will also have a private val observable shopping items so that is actually a live data object to simulate the behavior of this function because that will also return a live data object as you can see so observable shopping items is equal to immutable live data of type lists of type shopping item and we can initialize this with our shopping items list and we will also have such a live data object for our total price so private eval observable total price is equal to mutable life data of float and call the constructor on that and then a variable we will also have on this class will be a private var should return network error and we set that to false so as i said we can set this variable from the outside layer on to determine if our function should return an error or not so if our viewmodel is behaving as it should when there is no network error end as well if it is behaving as it should if there is a network error so we simply create a function here for that to set that variable so function set should return network error and in this function we simply said should we turn network error to the value we passed here actually like this okay now the next step is to actually let this class our fake shopping repository also inherit from the same interface we declared here so that is actually the reason why we implemented that interface here so we can let both our default shopping repository and our fake shopping repository implement that so both actually have the same functions so we simply implement shopping repository here and down here we press control i and implement all of those functions that we need to implement here we can remove the to do statements and actually also make a little space here like this so first of all the easiest functions here are actually observe all shopping items and observe total price because in here we will simply return our live data that we declare globally so we turn observable total price and in this we simply return observable shopping items and then in our insert shopping item and delete shopping item function we simply want to use our list of shopping items and simply add the item so as i said this shopping items list simply behaves in the exact same way as our room database in our real repository just that it does not save the items in a persistent storage of course but for a single test cases that is perfectly fine and much faster than saving that in a real database but to correctly simulate the behavior of our live data objects here of observable shopping items and observable total price we also should post the new value after adding an item to our list so i will actually create a function for that to refresh the live data basically so that can actually be a private function refresh shopping items actually refresh let's call it refresh live data and in here we will use our observable shopping items and post the value of our current shopping items so if we added a new shopping item then this will be included in the shopping items list afterwards we call refresh live data and we actually post the value of that shopping items with the new item in it and our observable total price we will also use that to post the value we can simply get from a function that i will also create here which will be called a get total price so let's actually write that right now private function get total price and that will return the float so the total price and in here we will simply return um shopping items dot sum by double sadly there is only a function to sum by double and not sum by float so we need to convert that here but that is not really a problem so actually you want to sum by it dot price dot to double and then we also need to convert that whole thing to a float again so dot to float and that will simply calculate the sum of all of our prices and return that in this function and if we post that value then we will simply just post the most up-to-date price value so now we can use that refresh live data function in this insert shopping item function and call that and we will do it the same way in our delete shopping item function so shopping items dot remove shopping item and we also call refresh live data and now there's only one function missing here in this fake shopping repository and that is our search for image function in which we simply want to simulate returning a success and an error resource so we simply return if um should return network error so if we want to return an error here then we will return resource.error with the message error that doesn't even matter here because the message could be anything here we just want to make sure that we emit such an error resource in case we should do that and we pass null for the data and else we simply return a dot success with an image response so we can simply create an empty image response here so that will take a list of image results so we can just pass an empty list here set total to zero and total hits to zero as well and that is now it for our fake shopping repository class let's actually go through that again as a little recap because that can be quite confusing in the beginning if you've never seen that before so as i said again we only created this class to later test our view model which we'll do in the next video because our view mode will take a repository as a parameter and then we can simply make that repository in the viewmodels constructor of type shopping repository so of type or of the type of our interface here so we can both pass our default shopping repository for that and also our fake shopping repository so that is why we made that interface actually and in our fake shopping repository we just simulate the the behavior of our real repository just to be able to check if our viewmodel is properly responding to the events that come from the repository so again we also don't test anything with our api here in this fake shopping repository there are actually ways to test apis but i'm not sure yet if i really want to include that in this playlist here because then it will quickly become very complex but maybe i'll do it i'll think about it you can write me in the comments if you really like that then i will take a look at it because i haven't learned that by myself so far but i could take a look in it and then show that as well if you really like it so i really hope everything was understandable here for you if not then don't mind to put a comment under this video and i can try to help you out with your questions have a really nice day see you next video bye bye
Info
Channel: Philipp Lackner
Views: 37,596
Rating: undefined 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: JsktEQvoHEs
Channel Id: undefined
Length: 21min 4sec (1264 seconds)
Published: Fri Aug 28 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.