The Ultimate Guide to Android Testing (Unit Tests, UI Tests, End-to-End Tests) - Clean Architecture

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey guys welcome back to new video in this video i will actually give you a full guide to testing a clean architecture app in case you missed my recent clean architecture video then you can see that here in which we essentially made this node app so if i quickly drag this over here i can see we we just have an app in which we can save nodes we can edit these nodes here we can give it a different color save it again and yeah just add new nodes we can all of these nodes and all that cool stuff so in case you missed that you you should really watch this this video in this video i will actually give you an introduction or rather full guide not an introduction it will be a full guide on how you can test this application so i will be going over unit testing integration testing and end-to-end testing so those are the three major types of tests we can have for an app and i will explain all of those here in detail why do we actually test our application i'm hearing that from beginners over and over again because writing test cases of course takes time and especially if you are a beginner and you never wrote test cases it quickly seems like it's a it's a waste of time because they don't do much but the the reason for that is usually that you haven't worked on on quite bigger projects yet because then test cases pay off so much you can't really not have test cases if you want to have a stable and solid app so the whole purpose of test cases is that we can verify that our app works but not only verify that our app works the thing with test cases is that we can verify it over and over again with just a single click so we just need to click run test cases and it will automatically run out of our test cases and tell us if our app works as expected so the bigger your app gets the more time you will actually save because you don't need to manually test everything because the thing is whenever you you change something in your source code it's not only this specific feature in which you change something um that you need to test you actually theoretically need to test everything because you could have broken some some other part of your app when you worked on on some kind of feature and test cases really um help you to detect that so much easier because it's it doesn't really cost you anything to run test cases except for some seconds so that is why you should really learn writing test cases that is a very very important skill in the industry and to get a job your employer will like it if you know testing so watch this video till the end let's talk a bit about the different types of test cases we actually have on the one hand there are unit tests unit tests are kind of the smallest tests and the the fastest tests these should make up the majority of your test cases in your app so approximately 70 of your test cases should be unit tests unit tests in the end just test single components of your app so that can be a single class and could also be a single function usually just a single function in a single class but they don't test how classes work together they they don't test any other larger behavior no a unit test could just be here in our app that the ordering actually works so that if we if we call our use case with an order title descending that this is actually ordering this list correctly that would be a unit test so it tests business logic then one layer on top of unit tests are so-called integration tests integration tests just test the behavior of two classes working together so those could be any two classes for example here if we test that if we click on this toggle section that it actually disappears and if we click it again that it appears so that would be an integration test if we just test this behavior that we can toggle this order section because it it tests how our actual ui so the composable interacts with our viewmodel because the viewmodel contains the state of the screen and if we click on this on this order section button here then the view model will actually toggle the state give the result to our ui again and the ui will then hide it accordingly so in the end just two components are working working together here and then there is one last thing um which are end-to-end tests they should make up about 10 of your test cases so integration tests 20 and unit tests 70 and to end-to-end tests are really um are quite big tests usually and they test they they really simulate user behavior here so that could for example be what we will do here that we click on this add button that we enter some title that we enter some content that we change the color that we then save the node that we then assert okay is the node really in our list that we then click on the node that we edited it again a little bit that we save it again we verify that the editing worked that would be an enter and test so just a normal user behavior or it could be that we click or that we add a node um then delete the node click undo and then just check if the node is in the list again that would also be an end to end test so those three types are actually the types that you will encounter over and over again when working with test cases and these are the ones that i will show you today so let's actually jump into coding here in case you haven't watched the video then you can get the the source code here from the link in this video's description make sure you get the the initial source code because i will also publish a final source code where i will include the test cases here that i will show you in this video so get the initial project from my github repository and i'm on the testing branch right now on which i will write the test cases first things first i want to add some dependencies for our test cases you can already see there is a test implementation here android test implementation what's the difference test implementation is for a local unit tests so these unit tests run on the local jvm here so they don't need your emulator to run that's why they all that's why they are also so quick because they don't need an android device to run if you take a look here in your project then you can see you have a test directory and an android test directory the test directory will contain your local unit tests so those will only be unit tests that you will find in here in a clean architecture project that is the test regarding your use cases and maybe some yeah utility classes where you just have single components of your app and then you have this android test directory which contains instrumented unit tests so not integration tests it's called instrumented unit tests those could also be unit tests but they they are related to android so they need android components such as the contacts such as a yeah ui tests for example that involve compose or fragments activities so basically any android components and these don't run on the jvm therefore so not only on the jvm but instead they need an emulator to actually run and when you then take a look here um you will see that we have test implementation android test implementation so if you use one of these then the following dependency will only apply for the single source set so for either test or android test so this dependency won't be included for a production build because it wouldn't make much sense to include test dependencies for our production app that's why we have android test implementation and test implementation and there's also debug implementation which is also used for some dependencies what i will do is i will actually remove the junit one and i'm not sure if i include the other ones and the dependencies i will paste now but doesn't really matter i will now paste testing dependencies here um and now go through these so on the one hand for a local unit test as i said here are these test implementations um this one should actually also be test implementation so just some core dependencies it might be that we don't need all of these here those are usually just uh the test dependencies i paid for all of my projects because you need these quite often um so yeah just some basic tests test classes that this will include then we have junit j unit is the testing framework for java and kotlin which we use here so junit is responsible for actually running your test cases and making sure or just checking that there is an uh an initial initial setup and then you do something and then it asserts that a specific condition is met that is in the end a test case that's the responsibility of junit then uh yeah that's also some more utility stuff i don't even know what exactly is in there we have cretins specific test dependencies so in case you want to test curtains you need that um truth is an assertion library by google which makes assertions much more beautiful than the default assertions by junit then we have a mock web server which we don't need here because we only deal with a local database but in case you need to test your api then you can use this mock web server to simulate network responses so you don't need to make the actual network calls we have marking libraries or whether just one marking library which is mark k i won't get into marking here in this video because we don't need it you can watch my testing playlist i go into mocking there so you just check my playlists and you will find that this will and just make sure that our our ui tests don't crash for a specific reason um i don't know what exactly that does but i needed to include it otherwise it always crashed then instrumentation test so for the android test source set we use hilt so i will show you how you can use hilt to write test cases and inject dependencies in test cases junit as well curtains as well yet the rest is pretty much the same so we can click sync now and hop into coding so let's actually finally start to write our first test case and that will be a unit test so unit tests are just the easiest tests and that is why i start with this what do we actually unit test here in our app well i already said in a clean architecture project we unit test our business logic i mean you always unit test your business logic but here the business logic is located in the use cases so those will actually be the classes that we unit tests here it could be that you have some just util classes that are not use cases uh yeah those would also be classes here with unit test so which of our use cases in our feature node can we actually unit test let's take a look at add node yeah we could do that we could for example test that if the node title is blank it really throws that exception and if the content is blank that it really throws that exception and if both are not blank we could test that it really inserts the node but i would actually not do that that will be your homework i will take the get notes use case because that is a bit more complex and here we just make sure that we will return the correct order if we pass the correct order so i mean if we pass or not or date descending that we actually order the list by date descending that is what we want to test here just for all different types of ordering so we can go up here to get notes on the class name press alt plus enter and then we can click create test we will name this and get a notes test so that's the convention just the class name you test followed by test we don't want to use jnet3 want to use junit4 there's also five but i think that doesn't work for instrumented unit tests you can make that work for local unit tests but i will just use obtained four we don't really get any big benefits here of using the unit 5. so just use that click ok and now it asks us in which source set in which folder we want to create that test that is a local unit test we don't need any android components here for this use case so we can put it in our test directory and click ok and there we go that's your first test case now the first thing when we were in the test case is to ask ourselves which dependencies do we actually need here what do we need to actually create a get nodes use case well we need a repository so that is what we actually need to create here and we of course need the the use case itself so let's start actually with that so that will be a private latent var get nodes of type and get nodes and then we can create a function which is called setup which we annotate with at before so what this will do is this will tell junit that this is our so called before function so it will run the setup function before every single test case and well it is used to set up things that we need for every test case such as just initializing some objects like our get nodes use case so get nodes is equal to get nodes just a new object and now we need to pass a repository well what kind of repository do we pass here we could pass our node repository implementation and uh yeah now we need a database how do we get the database we don't have one because we are of course here in our local unit test source set in which we don't have access to android components a room database is an android component it needs the context to be created so that doesn't work here and even if it would work we wouldn't do this because you don't want to test with your actual database or your actual api test cases should be quick and they shouldn't rely on any data writing logic and actually writing data persistently on your disk or uploading it to a real api at least not unit tests so we need to think of something different here so what i like to do when dealing with repositories for for testing is to create a so-called fake so a fake repository and i think i mentioned that when i actually implemented the app here so in the actual video that we often do that for test cases so fake repository is basically just something that simulates the actual behavior of our repository and we can then use that for a test case but under the hood it really only uses a simple list so we only save our whole database well which is not really big here in in a simple list because our use case of course doesn't really care where the data comes from it only needs to know which data it gets so what we will do is we will go to our test source set feature node go to new package data.repository right click and create a new um how do we call this fake note repository and now it comes in handy that our repository is actually an interface because now we can make this fake repository inherit or implement or not repository interface so we make sure that it has all these functions it needs we can press ctrl i ctrl a enter and now we can create our very own version of that repository that simulates our database so as i said in the end that will just be a simple list and i know if you never did testing then this will look very weird here what we do and it doesn't feel right but it is actually right trust me um so we can just have a private valve notes here which is a mutable list of notes when we get our notes we just want to return a flow of a list of nodes so we can just return a flow of our nodes that is how we could simulate that actually not of nodes we want to emit nodes here then to get a node by its id we can say we return nodes that find want to find the node that has the same id as we pass here that is how we could simulate that to insert a node we can say nodes.add and we add our node to the list and we can delete a node so nodes that remove nodes it's really as easy as that and that will now simulate our actual database with just a simple list and our use case will now make use of that repository and it will just get the same data as with a real repository just adjust adjusted for a single test case i mean of course not the same data as with the real repository so we don't get anything from our database now but it um for for the use case there is really no difference where the data comes from so now we can say in our test case private later network fake repository of type fake node repository we can then initialize that here fake net repository and that now does not take a parameter and then we can simply paste it here and that is now the reason why in our node use case we made that an interface because now we're not forced to pass our actual implementation that uses the database here in our test case instead we are free to actually create our very own fake version of that for test cases and we don't need to change anything in our use case for that now is there actually more setup that we need here to test the ordering of our nodes yes there is because we of course need to have some nodes in our repository to be able to check if the ordering was correct so what i will do is before every single test case so in this function i will just populate pre-populate our fake database here with some notes so we can say val notes to insert it's just a mutable list of notes of a note and then how i will do this to check this is i will just have a range from a to z then we can save for each actually for each index so we have our index and our character and we will just insert 26 nodes here for each single character so notes to insert that add we can create a node title will be the character.2 string i'm just doing this to to have something we can actually check with if the order is correct so we just insert nodes from a to z so we can check okay if node with title a is actually before node with title b then we can say okay the order is correct content will also be cedar to string then we have the timestamp which will just be the index too long and the color will be index so it really doesn't matter that we enter some dump values here all we really want is to verify that the order is correct now if you insert these nodes here in the list like that these of course are already ordered that is not what we want so it wouldn't be a surprise if this makes the test case pass so what we want to do after we inserted these we can say nodes to insert that shuffle so we just shuffle our whole list to make sure we have a random order and then we can simply say um notes to insert for each and we just insert every single note into a repository so fake repository insert node and we pass it that is now a suspend function so what we need to do is we need to execute that and run blocking so just put that in here and then we're actually good to go now we wrote the setup for our test cases and the next step is to actually write the test cases so let's do that down here whenever we have a test case to just tell unit hey that's a test case re-annotate this with that test then we have a function so each test case is a function and the cool thing for local unit tests is that we can use these um backticks to name our functions because we don't really call this function on our own so they don't need any really good names instead they should just tell us what the function tests and the name i give my test cases is always first um what do we do with this test case and then what should it return because then it will when we run our test cases all the test cases will appear and it will say okay order list by title ascending correct order so what we actually expect to happen and if there if it fails there will be a cross and we will see okay the order seems to not be correct so we can say order nodes by title uh ascending let's start with that correct order and now in here we actually need to write the logic for the test case which is very simple in this case first we actually need to call our use case with the order we want to check for so their nodes is equal to get nodes passing node order dot title so we want to order by title as sending so we say title ascending and because that returns a flow and not a list we want to say that first so we just listen to the first emission here of that of that use case and because that's a curtain or just a suspend function i mean we need to execute that in a croutine for which we can use run blocking in test cases now so far so good we have our list of nodes with the given order so we said okay we want to order by turtle ascending and now what we want to test is if the order is really by title and ascending so now we need to write the logic for that and for that i will just use a simple for loop so we say for i in node.size to actually in a zero to node start size minus two because now we will take a look at two nodes that are next to each other and just check that the first note is actually less than the second note so in this case according to the title so that the the title string is alphabetically sorted correctly you can say so let's take a look how this works we can just say assert that and that is an assertion function so here we now want to make sure something and if this fails our test case will fail if it succeeds our test case will succeed and there is an assert that function in junit which i don't want to use because we use our truth library that we imported so we're going to remove the junit assert block and then re-import that from this truth library then what do we want to assert we want to assert that nodes i dot title is actually less than nodes i plus one dot title so we now loop over our whole list and we make sure that it's in it's in the correct order that's all we do here if we now execute the test case by clicking on this icon here click run uh get notes test then you will see it will instantiate that and actually run the test case and if we did everything correctly it will pass and you can see it actually passes there is a beautiful check mark so let's imagine we actually now go to our use cases use case and now we accidentally change something or in future we change this use case and we say here for the title ascending sorted by descending now with our test case we just need to execute that again to detect that and that we actually made a mistake here and that that this is actually um covered by our test case that we see hey this this doesn't work as expected you can see now it fails it expected to be less than less than y but was z so that's just uh what was in our list but in the end then we see oh something is wrong we need to go to a use case and actually fix the back here and change it back to sorted by and just for completeness uh let's let's finish this testcase class and do this for the rest of our for the rest of our values we can order by it's very easy just copy and paste here so we paste this actually three four five six times in total then we want to order by title descending so we change this to descending then we make sure that it's actually greater than if we if we are ordering descending then we order by date ascending so we swap this out with date we make sure that we have date here actually timestamp it's called your timestamp then we have date descending date descending timestamp and timestamp then here we have color ascending so color color and color and we have color descending descending color and color so that was really quick now we have test cases that fully cover our use case or at least these sorting functions here and if we want to run all of these test cases we need to scroll up to our get notes test and click here on this icon and click run get notes test and this will now run all of our test cases you will see they will hopefully all pass and no they actually don't um seems like i did a mistake let's click on that order notes by date descending correct order yeah you see i did a mistake because i forgot to change this to is greater than and here as well is greater than but if we now rerun this then every test should actually pass and there you go and this is really the best the best thing about test cases if you see this you you almost get an orgasm so that is already it for our unit test as a homework in regards to unit test the video is not over yet um you can test this ad node use case so you want to write three test cases here as i already said you want to test okay if the title is blank do we really get an exception here if the content is blank do we really get an exception and if we have a valid title and valid content do we really insert the node in our repository so have fun trying that out at home and really do that otherwise you really don't learn the next type of tests we will deal with here are so-called integration tests as i already explained so in our case here we will just test the interaction between our node screen and our review model so we will just write one ui ui test here for the note screen you will see what that will do actually i think i already said that it will test that if we uh click on the on the sort icon that it actually toggles that section so it will test on the one hand dui and on the other hand the interaction with the ui with a view model it doesn't mean that all integration tests deal with ui and view models it's a common example in android but it could also as well just be two other types of classes that are that somehow work together for that i actually want to go to presentation notes note screen scroll up to our function signature and again press alt plus enter we say create test to create a test case i don't want to name this node screen kt test i just want to call it node screen test we can say ok this time it's a it uses android components such as jetpack compose so we want to put that in android test so every time you deal with ui tests then you definitely have an instrumented unit test or just an instrumented test that needs to be in the in the android test directory so we press ok there we go we have our empty test class and let's see what we will do here so for instrument tests so for all tests in the android test directory we will actually use hilt to inject our dependencies because these tests can often involve quite some dependencies that we kind of want to inject here and it's important to say that we can inject our normal dependencies from our normal di module or all of our modules actually so if we just set up dagger hill for our test cases now and we inject stuff that we provide here then that works but usually we don't want that because our normal modules involve our our actual production dependencies such as our database we don't want to have the real database in our test cases we don't want to have the real repository in our test cases um so what we will do is we will actually create a test module in which we kind of override these dependencies here to provide this specifically for our test cases and that is what we will do the first step to set up hill for test cases and that is always quite a bit of setup is to annotate the test case with ad healed android test then the next step will be to create our test module so we want to go down to android test create a package di and it's it's quite important that you use the same package structure for your test cases as you use for your actual production app because that just uh helps you much more to to find stuff in the test directory we can then actually take our app module and simply copy it in here in our di package call it test app module and then just adjust it at the places where we actually need to adjust it so the first thing is of course we need to change our database here do we even need a database here in our integration and end-to-end tests well we don't need that but i kind of want a database here actually and the cool thing about room is that it offers a so-called in-memory database so it's basically just a database that is well in memory so we don't write data to persistent storage that it will also keep after after relaunching the device no it's just just like a list so we can we can just treat it as a normal database and we yeah it basically then just gets cleared after the test case is over so that way we get the closest to our actual setup um while still keeping the the the fastness can you say that um while keeping how fast our test cases run because it's in memory um so what we want to do is instead of database builder i want to say in memory builder in memory database builder that doesn't need a name here so we can remove that and that's already it so now we for a test case we provide that in memory database and that will also be provided for a repository so here we will actually take the same repository as we use for our production app because that just takes the the note dow which we can get from our database here and yeah just our node use cases which will also stay the same and just take this repository instance so how does this work now we have our test app module and the next step is to to tell our test case we want to actually use that test app module and not our actual app module so what we do is we say add uninstall modules so we can say hey dagger please don't use these modules and we just put in our app module so by default it would just look in in all of our directories for modules and it would use our test app module and our app module but it would lead to some conflicts because we defined some dependencies multiple times so what we do is we say okay please don't use the app module so it will only find the test app module and we'll use its dependencies here that we just provided for this test case and for our other test cases now one more thing when we set up hill for test cases is that we need to create a custom test run class because um you might remember that when we set up hill here then we need to annotate our application with this hilt android app and in test cases um we don't use our node app here as a root application class instead there will be a custom one i think from junit or so and that won't have this annotation held android app so it won't really work so we need to create a test runner for which we will define our an actual application class that has that annotation so what we will do is we will right click here on our android test root directory create a hilt test runner that will be a class will inherit from a j unit runner is it android j unit android j unit runner this one then in here we can override as a new application and you can see we return super new application and here we have a class name of the actual application that we use for this test runner instead of this class name we want to say hilt test application double colon class java and because it's the name we want to say that name so that is how we can define this custom test runner we know we also need to register that in our build.gradle file that the unit actually uses that so we go to build gradle app scroll up to this test instrumentation runner here and you can see right now it's set to the default junit runner so we want to go to hill test runner um copy our package name and simply paste it in here and say dot build test runner or whatever you call your class make sure it's exactly the same because otherwise it won't work it won't find it we click synchronize now we can close this we can close this and now we just made sure that we use this custom hilt test runner for all of our instrumented tests so let's now actually start working on our actual test case on our ui test so that was a lot of stuff that we need to set up once but not for every test case so what comes now is actually what you need to do for every test class not for every test case um and of course this stuff as well but this test runner is just once for your application so what we will do is we first kind of need to make sure that we can inject dependencies here in this test case with dagger heal sadly this annotation isn't enough we also need to define a so-called rule so in junit we have different types of rules um that we can define that will just give us specific behavior for our test cases so there could for example be an activity rule that helps us to to do activity stuff like sending intents receiving intent results and as well as that there is also a hilt rule that gives us the behavior to actually inject dependencies so we will say add get colon rule and then we say val hilt rule is equal to healed android rule and here we need to provide the test instance which is just this so with this rule we can actually then call an inject function our before function so before every test case to just make sure that we properly inject the dependencies that's not the only rule we need here because we deal with compose we want to have a screen we want to simulate a new eye test here basically and for that we need the the compose test rule so that we can actually simulate clicks that we can simulate swipes that we can make assertions on views that we can actually find views or composables rather on our screen for that we need the compose test rule so again at get the val compose rule and that will be create android compose rule there's also no the normal compose rule that will just um give us the behavior that it will yeah it will just launch in a new component activity so just a normal compose type activity and we can put composables in that however we want the android compose rule because with that you can see we can specify our own activity that we want to launch that's important because the activity should also have this annotation here android entry point if we want to inject dependencies in that such as our review model so we need the android compose rule to make sure we actually launch our main activity and not any other random activity and yeah to actually make sure that this rule applies first so before this one because we first want to actually inject our dependencies we need to define an order so we can say order is 0 for this and the order is 1 for this so we say okay please apply this rule before this rule and now we can make it i'll take these two rules and do stuff with these in our test case so the first thing i usually do is just use implementing the setup function for a test case so at before we already have that function setup and what do we want to do here so first of all we want to inject the dependencies we need to do that actively with our hilt rule by saying hilt rule dot inject so that will just make sure that whatever we inject here in this test class or whatever we inject in dependencies we use here such as our composables will just be injected and the next step what we also want to do in setup is we actually want to set up the the screen so we can now use our compose rule and say set content and here we set our custom content for this specific test case that contains the stuff we actually want to test and i always make my test cases as isolated as possible so we don't want to use our actual app here that we set the content to we could do that but we really only want to involve the components that that we actually test here in this test class so in that case that's only the the node screen we still want to wrap that around an f to an f host object actually because um that way hilt will automatically inject our view models because these are bound to the navigation graph so we want to get an f controller instance first remember nav controller then we want to assign our theme so uh clean architecture notif theme and then we have the nav host passing our nav controller and we want to set it start destination let's do that in the next line we use our screen class to get the route for that that's the note screen that we wrote and then we can define our actual composable here so we will now only have one composable for the node screen because yeah that that's what i mean with as isolated as possible we don't include our add edit node screen here in this nav host so the route will be again screen note screen route and then in here we just put in our node screen passing the nav controller um then we need this annotation experimental stuff annotation here and that's actually the basic setup here for this ui test so before every single test case runs we just make sure that we inject the dependencies and that we set the content to this specific composable so in the enter node screen and now in the actual test cases we can make assertions and actually take actions on this node screen so we can simulate clicks on a specific icon and then make sure okay is now this order section actually open or not that's what we want to do so down here test function click toggle order section is visible so that is what we want to test here for uh instrumental tests we sadly cannot use this backtick way of writing these function names so i usually just do it like that with underscores to separate what we actually do and what we expect there is no general right or wrong how you name your test cases different people will do it differently i will teach you how i do it how i like it and that is exactly how i do it and i will just do one ui test here for this note screen feel free to extend that but you should just get how it works and of course we will then work on the end to end test which will be more more detailed and which will contain more assertions and stuff so let's think about how ui test actually works every single ui test first contains a part where it needs to find a specific ui component in our ui so specific composable it should actually either do an action on or do an assertion on so we can either say okay find this button click on it or we can say find this text field or put some text in it or if you want to do an assertion is we could say okay find this text field and make sure the text is actually android find this button and make sure it is actually clickable that that would be an assertion so we first need to find the composable we actually want to do something with and that is in this case the order section so when we freshly launch our note screen we want to first make sure that the the order section is not initially displaying so how we can do that is we use our compose we will we use that always here dot on node and now you have um now you can see that we have a bunch of so-called uh matchers i think it is so we can we can now use one of these functions that will that will help us to match either just one composable or multiple ones so we can say on all nodes then we can pass such a matcher to to just determine hey all nodes that maybe have this specific text or contain this text or we just have one node with either a content description a tag or a text so if you have a text field that already contains a specific text you can use this on node with text function if you have an image with a content description you can use this function but in our case our actual order section doesn't really have a content description and it doesn't really contain a text what we actually need here is we need on node with tag and that allows us to pass a custom test tag to associate our composables with uh yeah just to make sure that we find a composable that has a specific tag and that is only relevant for testing so we can go to node screen scroll down here to our order section and for the modifier we can set a so-called test tag just to make sure that we find this order section in our test cases so actually when you have a content description or a text you can find your composable with then it's better to use that because these test tags just um yeah it can make your production code a bit messy because they don't do anything for the production and stuff but if you don't have that then this is the only real way so we only define a test tag here for that i like to create a constant class maybe in a core package so just a core dot util let's call it like that and here we just have a constant class called test tags then we can say cons val order section maybe and then we can assign that here so test text order section so far so good then we can put that in here so test tags order section and now we can we can assert something here we can do something with that order section that we now either found or not but we actually want to assert that it's that it was not found so we can say assert does not exist so with that we just make sure try to find the other section but please make sure that it that you actually don't find it well the next step is to actually click on our toggle icon so on our sort icon that will now make the order section visible so we can say compose rule on a node and this time we actually have a content description so on out with content description and that is sort ideally you should use string resources here just to make sure that when you change this content description in your note screen where is it here for this icon we're going to click on and that it also changes in your test cases um i think i said that in the video that i just keep it simple there in the production level app you would use string resources and also if you want to access the application context in instrumented unit tests then you can do that using application provider get application contacts and you just pass a context so that is how you would get the context in a test case and with that you could also get access to resources of course only for the android test directory so now that we have this that we found this icon we want to click on we just want to say perform click so then we simulate that click on that composable and then we now want to assert that this order section is actually visible after clicking on this icon so we can copy this line and instead of asserting that it does not exist we can assert is displayed so that way we just make sure okay please check if it is displayed on our screen and that's already our very first integration ui test so let's run this i'm not sure maybe i missed something we will see i'll just run this here and we yeah let's check if it actually succeeds so let's see test case is launching it's installing the app and you should now see how actually the compose testing library simulates all these actions so now the test composable is launching it clicks that the other section was visible and you can see our test succeeds so that works fine and if we would actually now change our production level code that it doesn't switch the order section then of course the test case wouldn't work and it would actually catch that so of course um this looks like a lot of setup for a single test class um yes it is but don't forget that usually you don't just have a single test that is just for simplicity here um you could do more test cases here take it as as a homework for example what could you test you could test that you can actually toggle this order section that you can select every single radio button that you can again close it that's also something you would test and there's actually not so much more because the other stuff that we could test here with this list of nodes will be already tested in our end-to-end tests so that is actually what comes next but of course if you have a screen with many ui potential ui operations then there is a lot of stuff to test so next step entry and testing so for that i will actually create a class in our yeah in our notes package or no actually in our presentation package because it involves multiple screens so new card and class of file nodes and to end test i will call it and select class so now the last big step here for test cases are so called end-to-end tests i already explained that a bit this will test the actual user interaction so this will try to simulate what a user would really do in your app and not just toggling a section or so instead that could really be like in our case what we will do is hey click on this floating action button actually create a new node click on save make sure that the node is now in the list click on the node update the node click on save again and make sure that the update is also in in the actual list so quite big test cases here that can involve multiple screens and the test case setup here will actually be exactly the same so we first have hilt android test you can see now that will be a lot faster because we already did that once we can go to our node screen test copy our two rules and we actually also need this uninstall annotation so uninstall modules app module double colon class and we have a before function so setup in which we say hilt rule data inject and now for the actual content here we will again set up a custom nav host which will in the end just be what we have in main activity um so i will just copy this but i still like to copy it and not make this a composable because it's it's not usual that you only have two screens in your app and if we only want the ui test that involves only these two screens then i would still make that a custom navos for your test case so we can just copy that we can say compose rule set content we make sure that we wrap that in our theme and then we paste it in here so quite some setup here for the composer builds we get an error here for the annotation but that should already be it so we now make sure that we have a nav host that involves both both of our screens here so we can actually make sure that we navigate between these two screens then as i said the first entry and test label right here will be that we save a new node and then edit it afterwards and just make sure that the editing also works and of course the saving as well so we can go down here and create that that will be a little bit bigger test now function safe new node edit afterwards that is how i will call this and well what is the first step we want to do first to actually add a new node we need to click on the floating action button so compose test rule or just compose rule um on node with content description which will be add here that's the content description of the icon of the floating action button and we want to perform a click here so i'm going to click on that then i make a little space here because uh now we are on the next screen just to distinguish that a bit now on the actual at added node screen what do we want to do here we want to actually enter some text in our title text field in our content text field and then click save so how do we find our title and context field well for that we again kind of need test text i'm not too sure if we can use the hint for that if it will if that will allow it to enter text because the hint is actually just a text composable and we can't enter text in the text composable i will just create some test tags here if you want feel free to try that out at home if that works we will have the title text field and we will have the content text field like that then we need to apply that in our code so at edit node that will be in our transparent hint text field where we just need to be able to pass a test tag here and to apply that that actually needs to be on our basic text field not on the outer box so we cannot put this in this modifier we pass from the outside instead it needs to be in this modifier so that test tag we just specify the test tag then in our add edit node screen let's open that scroll down to where we create these transparent in text fields and here we can then easily just assign a test tag using test tags title text field and we have one for the content okay back to our end to end test we want to say compose rule on node with tag test tags title text field and we want to say perform text input so that will just add the text that we specify to this title text field and i will rearrange that a bit so for example test title then we can make the same with the content so we enter some content call it test content and now we're actually going to click save you could also change the color here but i won't do any assertions with that so we can say compose rule on node with content description again which will be safe here we perform a click on that and that will now in an if it works it will navigate away from this add edit no screen and actually get us back to the note screen with our list where it should display that new note so again i make a one line space here to indicate we're now on another screen so we now want to make sure that there is actually any type of note in our screen in our yeah in our screen that contains this text so we can say compose rule or note with text test title assert is displayed so that way we make sure okay the note is is now actually in our list the next step is that we use the compose rule and click on that node so our node with text test title perform click because now we so we click on that node and we want to edit it now then we are on the another screen again so the add edit node screen we want to make sure that now the title and context fields actually contains the title and content of that node so compose rule on node with text or actually not text or not with tag we use our test text again title text field and here we want to make sure a search text equals so we can we can say okay we make sure that the text in that title text field is test title so what we actually used for the title for that note test title we do the same for the content text field so we say content text field test content and if that fits we want to change one of these so the test title we're going to add some text to that so we can copy this i want to say perform text input so as i said that we'll just edit um so for example just a two so we have test title two then we want to click save again so compose rule on node with content description save perform click and now we just want to make sure can we find a node in the list that that has the text test title 2. so compose rule on node with text test title two assert is displayed and that's your very first end-to-end test here you see that is uh quite a bit longer than what we did here for this integration test so again here we can actually write some comments click on fab to save a new node or rather to get to add note screen here we enter texts in title and content text fields here we can say we save the node then make sure there is a node in the list with our title and content then what we do here is um we we actually also click on it here so click on note to edit it make sure title and content text fields contain note title and content then we wanna oops not here then we wanna add the text to to the title text field save the node or rather update the node and finally make sure the update was applied to the list that's probably what we do here so also people who see this in github can easily understand that let's actually run this test case we haven't done that yet and also open the emulator because it's quite exciting to see that the first time so let's see hopefully that works you can see we actually have our screen uh oh and it failed okay did we miss something failed to perform a gesture um expected exactly one node but could not finally know that satisfies content description at so it seems like it can't find our floating action button so let's quickly check that in our note screen and oh it's actually called add node you see so let's rename that to add and launch our test case again and that should hopefully do it if i didn't mess up any other content descriptions and that by the way wouldn't have happened if you would have had uh string resources so you see now yeah now we get to that edit note screen and it failed again let's see what it is now now it can't find a content description save did i call it now save node or what um let's check our add edit node screen floating action button i call it safe node indeed so let's launch it again but i think we we have no more content descriptions here so it should be fine and the app is launching add the node update it and now you can see now it actually worked so yeah test case passed congratulations i will write one more entry and test here just to give you one more example but i won't explain it in that much detail as that one because the video is already already so long so what i will actually do is i will add three nodes then i will change the order and we make sure that the order is also applied in the list so test function save new notes order by title descending let's just test that so we add new nodes then we order by title descending and we make sure that the order is actually correct so first of all i want to have a for loop here i in one two three so we just add three notes compose rule we can actually copy the stuff from up here this one until we save the note put that in here just do that three times make sure that we just uh set the title here to idle tostring content as well so we just have different titles for each note then here at this point we we're sure that we have three notes in our list so we want to now toggle the order section or i would say we first actually assert that these three notes are in our list so on actually compose rule on a note with text one assert is displayed duplicate that twice do that for our second and third node as well and now if that's the case we can say okay we wanna actually change the order so compose rule [Music] on node with content description let's do it like that this will be sort so order icon perform click and then we kind of need to find our radio buttons here because we need to click in the actual button bubble and not on the text so i will assign a content description for these as well on note with content description um just title so the name of the radio button the dot perform click so make sure that we order by title and then we make sure that we order by title descending so we say descending of course we didn't assign that in our source code so let's do that in a notes screen actually not in default radio button and for the radio button we can also assign a content description using a modifier modifier.semantics and here we can say content description is just equal to um what is it the text so the the text next to the radio button so here i like this semantics and content description more because it actually also gives us the accessibility benefits of using content description instead of just using a test tag which wouldn't give us these benefits so here we made sure that we have these uh corner descriptions we clicked on descending so the order should now be applied and we want to make sure that it is actually applied so we want to say compose rule this time on all nodes with tag and here we need a new tag for a specific node item let's again create that in our test text node item and apply that here in our node item composable modifier test tag testtext.node item so on all nodes with the tag node item that will give us now a list of items it can find so if there are multiple nodes it will give us all these nodes but we only want to get the first one so we can index that at zero and we can make sure that the text contains three because the first node it should find so at index zero should actually have the text 3 because we ordered by title descending so the 3 should be on top and the 1 should be at the bottom so let's do that for the other two composables at index 1 and two and have two and one here as the text so if we now launch this test case check our emulator and hopefully i didn't mess up any content descriptions again um so we add three nodes here one two three title descending and yeah it works test case passed if you want to run all your test cases you can just go to android test so all instrumented tests in that case right click run tests in com so that will run all tests in the specific package and yeah then you will have different tests oh we also have the example test here so now it just runs all your test cases and you can always do that with a single click to verify that your application works and the best thing is really that you get all these check marks in the best case if you want to run all your unit tests you just do the same with the test directory here we don't need to take a look at the emulator because they don't run on the emulator so these always take a bit to set up but then these run super quickly you can see six milliseconds of four milliseconds too and so and so that's the purpose of unit tests to just run very quickly and you have a lot of them so that is it for this video in case you actually like this testing stuff and clean architecture stuff android kotlin and you want to learn more about that for free then you will definitely like my email newsletter so you will get my tips right into your inbox so you want to check down the link to that newsletter where you can sign up for free and yeah then you will get all these kotlin android advice in in in your inbox you will definitely benefit from that and i wish you an excellent day see you next video bye bye
Info
Channel: Philipp Lackner
Views: 11,836
Rating: undefined out of 5
Keywords: android, tutorial, philip, philipp, filipp, filip, fillip, fillipp, phillipp, phillip, lackener, leckener, leckner, lackner, kotlin, mobile
Id: nDCCwyS0_MQ
Channel Id: undefined
Length: 70min 14sec (4214 seconds)
Published: Wed Sep 29 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.