Unit Testing Flows - The Ultimate Guide to Kotlin Flows (Part 4)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey guys welcome back to new video and actually to part number four of the ultimate guide to kotlin flows in this last video of this playlist i will actually show you how you can unit test your flows because in pretty much every project you will have to deal with flows and that means you will also have to uni-test these if you actually want to do it right so that's what i'm going to show you today i am still in the project of the last video and what we are now going to do is if you remember we have that countdown flow which basically just has a starting value here of 5 and then simply counts down and i would actually like to write a unit test for this flow to actually test if the countdown is properly working so if we're properly counting down just second by second and this is not really trivial because you know there is some kind of delay that we wouldn't like to have on our unit tests there are different emissions here at different times and there's just some stuff going on so this type of unit test is not super easy you will fully understand that in a few minutes so let's actually first include some more dependencies that we need to unit test that so now build a gradle app file you will have to include these dependencies which you can find in my github repository down below let's go through these on the one hand we have turbine this is actually an optional dependency here but it's independent it's a dependency i use in almost all of my projects so it's actually a library that makes testing flows extremely easy you will see why that is then i use truth that's a google library that comes with a lot better testing assertions than the default junit assertions and we have curtainx crewteam's test which just comes with some test functionality that we need to properly test coroutines and in the end the flow is nothing else than a curtain that can emit multiple values over a period of time if you remember that from the first video let's synchronize this and then hop into our view model and actually put the cursor on main view model press alt plus insert it is and then we can select test to simply generate a test case for that i would call it maybe main view model test make sure to select j node four and we would like to have a setup function which is just called before every single test case then let's hit okay we want to generate that in our test source set so that's not an android test we don't need any android dependencies like the context or so to run this test no we would like to have local unit tests so clicking ok and there we go if we extend these imports we can simply remove this junit assert import because as i said we will use the truth library to handle that and i hope you're a little bit familiar to testing if not then i can really suggest watching my testing playlist first because testing flows is probably not the right entry point for unit testing at all like unit testing in general so first of all we're just going to declare the object under test so what do we want to test in this test class and that's our view model so private latent var viewmodel type main view model then in setup we would like to initialize that viewmodel or re-initialize that before every single test case so just to make sure that we have a brand new view model you can say for every single test case we don't want that a test case fails because there is still some state in the view model from a previous test case so we say main view model and then we're actually ready to write our test case we can annotate the function for that with at test and since that's a local unit test i like to have these two backticks and name my function with basically natural words and sentences that's pretty cool in kotlin so what do we want to test we want to test the countdown flow and we basically want to test that it properly counts down from five to zero so that's what our test case is actually about so basically how this now has to look like we need to trigger our flow we need to collect it and then we basically need to check every single second if the emission is correct so so far for the theory let's see how this now works as i said we include that turbine dependency which is a very awesome library since because of because of that library we can now simply say viewmodel um we want to test our countdown flow and that library now came with a function called test so that's a function you can now call it any type of flow and here in that you get a so-called flow turbine and the cool thing is if we would now just want to wait for an emission in that flow we can say val emission is equal to a weight item so this will now suspend this block of code until it actually gets and gets an item here so as soon as we actually emit the starting value there's a weight item function would assign the starting value to our emission in that case five and if we would now call a weight item again it would wait for the next emission so that would then be this emission so it would emit the current value minus minus so four just the next value that was counted down so that's pretty cool we still get an error here because it's that's suspend function that we can't execute directly in our test case there is luckily a very easy solution to that whenever you test curtains and test cases you use run blocking run blocking is something you typically would never use in a real app because it blocks your actual main thread but in the test case you want that you don't want that the test case finishes before a crew team that was launched in it so that's why we use run blocking to actually block the test case so run blocking and suddenly the error goes away so what do we actually now want to do i will actually delete this emission again and we actually want to have a loop here for i in let's say five down to zero because that's what our countdown actually reflects it counts down from five to zero and with these eyes we can now check if the actual value and the actual emission is equal to that i in the corresponding iteration so we want to say val emission is equal to weight item now we want to make sure that this item is actually the same as i so in the first iteration of this loop it should be 5 which is the value of i and the second iteration it should be 4 which is the value of i in the second iteration and so on i think you get it and because we use the truth library we can make assertions in a very readable way you can assert that input that choose the truth one here and we want to assert that this emission is equal to i and that's now how this works so we just await five items in total and make sure that the item is equal to i however there's still something wrong with this code because right now every time we run the test it would actually take five seconds to run because we have the delay block in here and that's of course not optimal let's say you would have a countdown timer of five minutes then you don't want your test case around five minutes to see if everything works so how can we actually prove this or rather make sure this is actually a real unit test that's quick now i actually come to a very important concept of android something you should always stick to and that is you should always inject your curating dispatchers wherever you need them the reason for that is that for test cases if we would actually like to skip delays we need a special test query in dispatcher because with that test dispatcher we can just say in the test case okay now you should actually skip one second and it will skip that so our test case will basically ignore delays but to make that work our crew routines in our main view model actually have to use that dispatcher and by default they just use the main dispatcher in local unit tests we don't have access to the main dispatcher because that's yeah it's related to the main looper which only exists on real android devices and since the local unit tests run on the jvm not on an emulator there is no main dispatcher so right now if you would run this it would most likely just crash you can see here we get an exception and it says module with main dispatcher had failed to initialize blah blah blah blah so the reason for that is just that we don't have that main dispatcher in local unit test but that's the default dispatcher for all crew routines so how can we fix that as i said we want to inject our dispatchers let's do this step by step and then it will actually be clear first of all we want to go to our root package not our test package or root package and create a content class called dispatcher provider that will be an interface and in this interface we define the different dispatchers we would like to use in our project typically that's on the one hand main io and default so now we have that interface which just abstracts out these curtain dispatchers and we can now create concrete implementations of that so we can simply do that right here and say we have a class default dispatchers that we use in our actual android project make sure that implements this patcher provider we can press ctrl i select everything here and override that and here we can just assign our normal dispatcher so main for main io for io and default for default with patchers io and finally dispatcher's default now if we go to our view model we now want to inject these dispatchers so that means we provide our dispatchers here which is now an instance of this patcher provider so this dispatcher's provider interface now contains the actual dispatchers we want to use so now since we did this we also need to apply these dispatchers for all of our curritins that potentially run here to do that for flow we can simply say flow on and now we can say this patch's main so since in our real app we will use these defaulters patches this dispatch's main will just refer to the normal dispatcher's main however we can now create a different version of this dispatcher provider for test cases that uses the test dispatcher for all of these and then our view model will suddenly use the test dispatchers for everything let's also make sure that our other routines use that this patch is main this patch is main and probably square number here as well this patch is main and of course for the other ones down here as well but i'll leave these because we don't call this here so won't lead to a problem this gives as a warning which comes from a linting tool we can ignore this here and jump back to our main view model test now you can see when we create that view model we actually need a parameter now we need to provide some kind of test dispatchers here so in our test source set let's open that we now create a new kotlin class called test dispatchers which also implements that dispatcher provider interface control i ctrl a and enter and now here we want to use a special test dispatcher so we can declare that here and set it to new test crew routine dispatcher need to add this experimental blah blah annotation and now for all of these dispatchers we simply pass our test dispatcher and when we now use such a test dispatcher's class in instead of this default dispatch's class for our viewmodel then in our test cases we suddenly use this test dispatcher for everything in our viewmodel like for for this flow for this viewmodelscope here because for the test dispatchers the main one refers to the test dispatcher so that's how this works so let's have that here light in that var um test dispatchers and here we simply pass these test dispatchers when we construct a review model and now what we can do is since with that we already make sure that this flow uses our test dispatcher and we also need this annotation here let's add that to the class but we still want to skip the delay here as i talked about and for that we can now simply use our test dispatchers refer to the test dispatcher we use in our review model and we can use advanced time by so we can now say we want to advance the time by a single second and then we should actually get an event or rather an admission here and we make sure the emission is then equal to i and something we can also do is if there are actually more events coming in after the last one here then it will actually throw an exception to fix that we just want to say cancel and consume remaining events let's just make sure that we properly finish this test scope here so if we now relaunch the test case here take a look down here and you can see it still fails latent property tests as patches has not been initialized oh of course i need to do that so test dispatches is equal to test dispatches and then let's relaunch that and try again and now you can see the test actually succeeded so that's pretty cool but i want to write another test case to show you something you often need to take care of and that is i want to actually test this career number function so let's take a look down here i'll minimize this then all that really happens here is we send an event into the shelf flow that squares our number we want to test that now that the number is really squared because that's a super simple example now let's go to our view model and write a test case for that function square number number properly squared i don't know that's weird naming because that's just such a simple test here we again want to use run blocking and this time we want to listen to events in our short flow so we call test on that one so here we get that emission by using a wait item again and we actually now need to trigger that sending that event manually so if we now assert that the mission is equal to let's say nine then nothing will happen because we of course need to send that event into that shared flow with the square number function so let's do that we say view model square number three and then we just make sure okay we actually expect that the emission is nine so just the three squared let's run this you can see the test case actually fails um unconsumed events found well it seems like there are some events that were unconsumed but we actually did consume these here didn't we well it's the same problem as i already explained in the last video we sent the event into the shared flow before we actually run this test block before we actively wait for the emission so what happens if we do it afterwards so actually make sure that we wait for the item and then we send the event while we wait for that let's see now the test case actually succeeds however that does not always need to be the case here with these things where you manually need to trigger an event um so something i actually always suggest is launching this in a separate routine so you can see the test function actually suspends and i would actually just like to have a job here say launch and put that in that job so that will just make this run independently of this so it actually doesn't suspend first and then send the event instead it will basically do both simultaneously and after the test case we could say job join and then we cancel the job and in here we could also say cancel and consume remaining events so if we now relaunch this then it should also succeed again and there you go the test case passed so that's how you also test shared flows that's a typical use case here that you first launch your job then send an event into it and then simply check whatever that event is so that's it for unit testing flows if you especially like these little tips like injecting these dispatchers which is not something you find all over the internet but that's actually pretty important then you will also love my free email newsletter because there will actually share much more of these these tips that not many people actually talk about so you can simply check the link down below and subscribe for that just entering your email rest is free you will receive regular emails right into your inbox and if you want to learn more about testing then i also have an ultimate testing guide on youtube which is free if you're interested in that then you can simply click here and watch that so enjoy
Info
Channel: Philipp Lackner
Views: 1,813
Rating: undefined out of 5
Keywords: android, tutorial, philip, philipp, filipp, filip, fillip, fillipp, phillipp, phillip, lackener, leckener, leckner, lackner, kotlin, mobile
Id: rk6aKkWqqcI
Channel Id: undefined
Length: 20min 5sec (1205 seconds)
Published: Thu Dec 16 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.