Testing Room Databases - Testing on Android - Part 6

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey guys welcome back to a new video in the last video we made this beautiful shopping dell class hero interface actually in which we implemented all of our functions we need to access our database in this video we will test this interface or actually our room database so if these functions here actually do what they should do and for that we now need to think about if this will be local unit tests so tests that run on the jvm or instrumented unit tests that require the android emulator so i give you a short amount of time here to think about that you can pause the video and now i'm going to give you the answer and that is this must be an instrumented test because creating a room database requires the context and actually theoretically we could also make this a local unit test because sqlite could also be accessed locally but in that case if you if we want to test sqlite databases locally then it will take the the version of sqlite that is installed on your local machine and not the sqlite version that is installed on your android device and we of course want to test if it is working on an android device so we should really make this an instrumented unit test here for that we will go inside of our java package then instead of our android tests are set here and usually when we write unit tests then we want to have the same package structure as in our normal package here so you can see we have our data package local and in here is our shopping dial and if you now want to write a test class for that shopping dow that should be in the same package structure so we go inside of our shopping list testing youtube here our root package basically of android test we create a new package here called um data.local so exactly the same structure as we have here and inside of that we create a new cotton folder class called shopping dao test and this will be a class and now we need to add some annotations on top of this test class here on the one hand we need to specify add 1 width and in the parentheses we write android junit 4 double colon class so what the heck does that mean so in general junit so the testing library is a library that is used to test java or kotlin code or any code that runs on the jvm basically and here we are not inside of such a plain java cotton environment because we are inside of an android environment and that is different as the jvm because we run our tests on the android emulator and not on the jvm at least if we are instead of this android test directory here so those are instrumented unit tests and they run on the android emulator because they need android components and with this ad run with annotation we just make sure that all these tests inside of this class will run on the emulator and actually also just to tell june that these tests are instrumented tests in here then the next annotation we need to add here is or we can add that is actually optional here but recommended that is at small test so if you remember in the first or second video i don't really remember but there we had that testing pyramid in which we had three kinds of test cases first of all unit tests which were the base the foundation of the permit then we had integrated tests that just test different components so how they interact with each other and we had ui tests which made up the top of the pyramid and the unit tests of that pyramid so the base are actually the small tests here so with that small test annotation we just make sure or we actually just tell the unit that what we write here are unit tests so if we would write an integrated test here and two integrated tests in the in this class then we would annotate this with add medium test or if we would write ui tests or ngn tests however you might want to call it we would call that a large test but this will be a small test and the differences between those test cases you can actually see in this table here you can pause the video and compare that i won't go through that now because it's pretty self-explanatory i think anyways let's actually jump into the test class here and think about what do we need to test our room database so we of course need our database and we should create a reference to that here so private late in it var database which is a shopping item database and if you remember from the last video or second last then we shouldn't initialize this right here so globally because we should write a function that sets that database up before every test case that we just annotate with the add before annotation just to make sure that we have an entirely new database for each test case because that should really be independent and especially with databases if we insert something into that database then we don't want that this item is still in the database if we are inside of another test case because that could hardly manipulate that outcome of the test case basically so we'll also have that private latent var for the dao object so dao which is a shopping dial and then we will write that function i talked about function setup in which we just create that database now and we annotate that function with at b4 so that will just be executed before every test case inside of this class and in here we use database is equal to room dot and usually you use that database builder now but we use this in memory database builder so the difference to the database builders actually that this is not a real database so well it is a database but in memory means it will only hold the database in the ram and not inside of the persistent storage so what we save inside of this database won't actually be saved in a real sense of saving it will only be safe for that test case and of course we don't want to set up a whole new database for each test case that just stays in persistent storage so that is why we use that in-memory database builder here and that will take the same parameters the context and you saw that in the last video how we can get that we can use application provider dot get application context here to get a reference to the context inside of a test case and the class of our database is shopping item database double colon class.java then afterwards we write dot allow main thread queries so that if we apply that function on that builder it will actually do exactly what the name says so we allow that we access this room database from the main thread well usually if you use a room database or any database you of course want to access and write through that database from a background thread because that can be can be a blocking call to write or read from the database but in test cases we explicitly want that that it runs on a single thread because if we have multi-threading in in test cases that could mean that these different threads manipulate each other so because we can't predict how the threads run at the same time this will be a little bit different for each test case and in the end as you know we want complete independence for each test case and that is why we execute this room database in the main thread so that all actions are executed one after another so after that we can finally call that build to get that database and we can also reference our dao by writing database dot shopping dao and then if we have a before function we often also want an after function so a function that is executed after every test case and in this case we simply want to close the database after each test case so after function and that is called teardown and in here we just call database dot close and let's let's actually take a look at our shopping dao which functions we want to test here that is our insert shopping item function and delete shopping item observe all shopping items basically every function we have inside of that shopping now we want to test and let's start with this insert shopping item function so we just test if we insert a shopping item in our database with this function if that works as we expect and you can also see that this is a suspend function so we need to execute that from within a crew routine then if we go back to our shopping test let's write the test case here annotate it with add test and call it function insert shopping item which must be executed in the croutine and as i said we don't want concurrency here in test cases so basically multi-threading and that is also why we will use run blocking for the crew routines here so if you don't know run blocking that is just a way to execute a crew routine in the main thread so that will actually block the main thread with suspend calls but we can execute suspend functions instead of that crew routine and usually we would just write run blocking here but as you can see here is also creatine called run blocking test which we will use because that is optimized for test cases basically so what optimize actually means here on the one hand this run blocking test function will just skip the delay function so if we have a function in which we delay a crew routine this run blocking test will just skip over that so it doesn't delay our actual test case and also if we press ctrl space here to see the functions we can execute you can see there are a bunch of functions that we don't have in the one blocking function itself so we have access to the current time of the crew routine we can advance the time by so we can basically modify the time of the crew routine which is pretty cool here and pretty useful if you want to test a crew team that needs this behavior that where you just want to change the current time and check if the crew team does what it should do in that case we could pause the whole dispatcher here we can resume it and all that stuff we don't need that here but you should still get in the habit of writing your crew routines that you use in test cases in this run blocking test function here so let's actually think about how we could test that insert shopping item function well that is not that difficult we just create a shopping item we will insert that in our database here and then we will simply read the items from our database and check if that item is contained there so we will first of all create a shopping item here well shopping item is equal to new shopping item and we can give that any name that doesn't matter here any amount let's say one any price one f um just any string for the image url and we want to give it an id as well just use one here and then we will use our dow and insert that shopping item with our function so that is the actual function that we test here if that works as expected and now how can we read our entries actually from the database so we don't have a function here in our dao that just gets an item by its id that would be useful but i also don't think it's a good idea to just create such a function for our test cases because we don't need that in our actual app but we have a function that observes all shopping items and that will also retrieve a list of all shopping items so we can just check if that item we inserted is contained in that list so let's actually do that rewrite val all shopping items is equal to dow a dot observe all shopping items but now we have a problem because that returns a live data and not the actual list and live data as you know runs asynchronous and we don't want that here in our test cases and luckily there is a super helpful class by google that helps us with that problem and i actually will just copy that that that file here that's not a class that's just a plain kotlin file with a function i will just paste this inside of this project here you can get this in the description from my github repository so i will explain what it does but i don't see any reason in implementing it here on our own because you can just reuse this class for every of your projects just copy and paste and we will paste that actually in our root package here of the android test directory so that will be called live data util android test press ok here and you can see i'm talking about this function here so as you can see that is a function called get all await value and that extends live data so we can just call that function on livedata objects so on the function we have here but let's actually try to understand that we have a parameter time which is equal to 2. so that basically specifies a timeout which is set to two seconds in this case so we also can specify the time unit here and we have a function after observe but actually for us here we don't need to change any of these parameters so we also don't need to provide parameters here in the end what this function does is it will wait until this live data we call this function on returns a value so if this actually takes longer than two seconds by default it will throw a timeout exception here and that will never be the case here in our in our case since we just get something from our room database and in the end we just want to get the value of a live data object and that is what this function does here so just make sure that you paste this file inside of your android test directory and then we can use that function in our shopping dow test on this observe all shopping items function here so because that is an extension function we can just call a dot get or a weight value you can see we choose that function here without the lambda block and we don't need to provide any parameters and if we then click on all shopping items press q control and q actually then you can see that is a list of shopping items so that is the list of shopping items that this observe function here returns and after that we can just work with that list now and there are actually also other ways to test live data which you might see something in the internet but google actually uses this function in all of their examples so i think that is a pretty good practice here if google uses that so now that we have that list of all shopping items we can write our assert function so now we want to actually assert that this item we created here is contained in that list so because we inserted that item it must be contained if the test should pass so we use assert that and we need to import that from the truth library as we used in the previous videos we want to assert that all shopping items dot contains our shopping item we created on top here and you can also see that we get a warning here with that run blocking test and if we press alt plus enter here you can see we should add this add experimental curitin's api annotation to our class um not to this function we actually need that several time in this class so we scroll up here to our class annotations and we add this annotation experimental crew routines api and then you can see the warning goes away probably because that is a pretty new function here we just should add that annotation and let's actually try to run the test case now by clicking on this little play symbol here and click on run insert shopping item then this will load and of course also launch our emulator here there we go so let's wait until our test case is actually run here on this device and gradle is taking ages here and now you can also see why you should always use um local unit tests on the jvm if you can do that so if you don't necessarily necessarily rely on android components you should always make that a local unit test let's take a look here and you can see our test failed surprise surprise this job has not completed yet what the heck does that actually mean well the problem here is actually our live data our live data makes us problems here because it is asynchronous by default and even though we use run blocking test here junit doesn't like that and to solve that problem we need to explicitly tell unit that we want to execute all of the code inside of this instead of this test class here one after another so one function after another basically in the same thread and we do that by specifying a so-called rule for a j unit and that will be a so-called instant task executor rule so var instant task executor rule and this one here and we set that to new instant task executor rule here that just doesn't take any parameters but we need to tell junit that this is actually the rule for this class by writing at get colon rule and if we now rerun that test case here let's take a look and wait until gradle is hopefully a little bit faster this time you can see the test case is running and this time it succeeded so that means our insert shopping item function works as expected so let's actually test the next function which is our delete function let's minimize that and create a function here at test function delete shopping item and also set that equal to run blocking test here we will create the exact same item again so we can just copy and paste that so that is again a totally new database that we have here because of this before annotation we just recreate the database in before each test function here so that is really independent and we can also re-insert an item with the same id basically and then we will use our dao to insert the shopping item we just created because if we don't have an item in our database we of course can't delete one and then right after afterwards we will delete that item again so delete shopping item and then we again get all shopping items by writing down or observe all shopping items dot get overweight value so that will again contain the list of all of our shopping items but this time this list should be empty because the only item we inserted here was deleted immediately afterwards so this time we assert that all shopping items dot does not contain this shopping item we created above so then we can run that and take a look if that hopefully succeeds and as you can see that succeeds so our second test case is also working properly and finally we also want to test the function to observe the total price sum we actually don't want to explicitly run a test case to test this observe all shopping items function because we already implement that also in our other test cases here so that actually must be working and it's also pretty hard to actually test that anyways let's actually test the other function here so add test function observe total price sum also equal to run blocking test and now let's actually think about how we want to test that let's first of all insert that shopping item again and let's actually insert this several times here so let's duplicate that with ctrl d name this shopping item 1 shopping item 2 and shopping item 3 and now we write some different amounts and prices for these different items so let's choose anything here that makes sense two times the price 10 then we have let's say four times let's also use a float number here for example 5.5 and something with zero for example and a very high price and now we want to insert those three items so dow dot insert shopping item shopping item 1 2 and 3 and this time we don't want to observe on all shopping items instead we want to observe on the total price sum so total price sum is equal to dow dot observed total price and you can see that now returns the live data of type float so actually the the total price so again we can call a dot get overweight value here and what do we actually want to assert now we want to assert that this total price sum dot is equal to and now we can check our amounts and prices so equal to 2 times 10 f plus 4 times 5.5 f and the third item is irrelevant because the amount is 0 so the the multiplication of 0 and 100 is also zero of course so the total price sum must actually be the same as this term here and if we now scroll to completely to the top we can actually run all of our test cases just because that looks so satisfying run that and take a look what it will do you can see hopefully that everything works and oh we get an error here um okay let's take a look here in our test case something must be wrong here and i already see the error here maybe you've already seen this and i thought what the is philip doing here but we actually inserted three items with the same id so it will actually replace each item every time we insert an item here so we just need to give it a different id each time here and if we now rerun our test cases here and then wait a little moment hopefully it should work right now and as you can see it does so that is actually the reward for writing test cases you get those check marks here which looks super satisfying i hope you enjoyed this video and learned something new i know this were a lot of new concepts here in this video with this room testing stuff instant test executor rule all that all those annotations here run blocking tests a lot of new stuff but i think that everything should get clear to you if not then just put your questions in the comments and i can try to help you out with that see you next video have a nice day bye bye
Info
Channel: Philipp Lackner
Views: 13,807
Rating: 5 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: xGbr9LOSbC0
Channel Id: undefined
Length: 24min 13sec (1453 seconds)
Published: Mon Aug 24 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.