Testing in Android a Zero to Hero Tutorial-Part 5
Testing Fragment/Activity interactions with Mockito & Espresso continued.
Let us write a test case for scenario : when user pick on a image, from ImagePickFragment, then we navigate user back to AddShoppingFragment and make sure the image url is available in ViewModel.
We start off by first creating an image picker adapter using recycler view and diffUtils, then we create a fragment factory so to pass constructors to our fragments when needed.
right click on ImagePickerFragment -> generate -> test case -> choose androidTest folder
Testing action image click in ImagePickerFragment which set image url in model view and navigate back to AddShoppingFragment
Since our fragments need constructor parameters which is supplied with the help of fragment factory we need to inject this in our test class as well, and the rest of our injections will be same as the ones in our other test classes.
@Inject
lateinit var fragmentFactory: ShoppingFragmentFactory
Now unlike our earlier test cases in which we simulated a button click with the help of onView() & perform() function using espresso, simulating a click through a recycler view is a bit different.
First we need a help of another dependency which needs to be added in build.gradle() file
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.3.0'
Now we have access to that function which simulates the click on a recyclerview, But this alone is not enough since we launch our fragment on a new container the data in the recyclerView will be empty. We also do not want to make any requests to our pixabay API , since it will make our test cases slow.
So we work around this by manually inserting a test url, which will satisfy our requirements.We can then check if this value matches the value from our view model. This is done is lines 23,29,30 in below code block.
line 24- Since we don't have a MockShoppingRepositoryAndroidTest in our androidTest folder we need to create one since we cannot share things between androidTest and test folders, we can copy the one already in test folder and paste it in our folder structure so we can use its functions.
line 33 to 38- After we insert that library we have access to RecyclerViewActions.actionOnItemAtPosition<”type”>(“item”,”action_perform”), which takes our viewholder type , which is of type ImageAdapter.ImageViewHolder. And then we have to pass the item we have to click on, mentioning 0 would mean to click on the very first item and finally we have to perform the action click()
line 40- Verifies that the previous fragment (AddShoppingFragment) was opened successfully
line 41- we check if the imageUrl in our mock repository contains the imageUrl we used in our test case.
Testing functionality which will enter text in textfield and action insert the new item in database and display in list.
We will add this test case in our already created AddShoppingFragmentTest.
line 3 to 8- First we need to declare our fake viewmodel with our mock shopping repository, since the real view model will contain a actual database,Then we launch our fragment using our custom launchFragmentInHiltContainer class.
line 9 to 15- using espresso we have a have a way to add text in textfield which is called replaceText(),and we perform the click as we did in earlier test cases, finally we assert that from our viewmodel the getShoppingItems data has the same values as the ones we entered above, if so then database insertion was successful and the test passes.
Testing swipe to delete functionality
We will experience a small adjustion when creating a test functionality for this , the reason is :
We need to allow our test cases to set view model to null, since we don't need it for test cases, but we cannot do this since our fragments instantiates our view model in the beginning of onViewCreated, and the swipe to delete functionality happens at end of fragment lifecycle.
shoppingViewModel = ViewModelProvider(requireActivity()).
get(ShoppingViewModel::class.java)
So to solve this we need to have a way to pass ViewModel and not declare it directly in fragment.
So to achieve the interchangeability of viewmodels between our test class and fragment class , we create another custom fragment factory for our test classes
In this new fragment factory class we will instantiate the ShoppingListFragment with a ViewModel that was created with the Mock Repository.
So we start off by handling this scenario in our ShoppingListFragment
In above code block we allow the shoppingViewModel to be null which can be used as a flag to tell that currently test case in use, when it does have a value it will be because a real fragment is in use.
Since we have already created a shopping fragment factory to handle our real fragment scenarios, we will copy that and paste it in the androidTest folder with one small modification.
Difference in line 11 to 13- We simply just pass MockShoppingRepositoryAndroidTest() into ShoppingViewModel(), to say its a test class and in the real ShoppingFragmentFactory we don't pass anything except the adapter.
Now we can write our test case.
We have to inject the ShoppingFragmentFactoryAndroidTest , since this testing requires us to handle ViewModel appropriately.
@Inject
lateinit var testFragmentFactoryAndroidTest: ShoppingFragmentFactoryAndroidTest
test case:
line 3- to delete an item first we need to insert a item into our database
line 4- we declare a instance to our real ShoppingViewModel and set it to nullable.
line 6- instead of passing our real fragmentFactory we pass our testFragmentFactoryAndroidTest
line 12 to 17- we use espresso to simulate recyclerview action and use inbuilt espresso function swipeLeft() to replicate a left swipe just like our real world app scenario.
line 19- and at last we check if its empty after deleting, if it is then its a successful test case.
Now when we run our test class we get :
This wraps our tutorial series if you read through till the end thank you, and i hope you were able to learn a few a things or add anything into you cavalry.
Learning testing in android is a difficult topic to say the least but a worthwhile one.
I will link my github for this complete project since i only mostly concentrated on testing and not implementation of UI and functionality
I will also link some great online references which helped me learn testing and made it a possibility to write this tutorial series to my best knowledge.
Any feedback would be appreciated
https://developer.android.com/training/testing/fundamentals