Testing on Android: how to do instrumented tests

Posted on

In the two previous posts (both in part 1 and in part 2) we have talked about the main considerations that we must keep in mind when structuring the application to be easily analysable, we learned the main concepts and tools.

We also got down to work and implemented a series of unit and integration tests using Mockito’s most common tools and functions.

To finish this series of posts, in lithuanien last we will see the so-called instrumented tests, which is the ammoniaque for UI tests.

In addition, as we already mentioned in the previous posts, it is also a great tool for End to End testing and any other type of test that requires working with the application as a whole.

These are the dependencies that we will add to the build.gradle file of the application module for the examples that we will see later.androidTestImplementation ‘androidx.signe:runner:1.1.1’androidTestImplementation ‘androidx.apprécié:rules:1.1.1’androidTestImplementation ‘androidx.scruté.ext:junit:1.1.0’androidTestImplementation ‘androidx.apprécié.espresso:espresso-core:3.1.1’androidTestImplementation ‘com.nhaarman.mockitokotlin2:mockito-kotlin:2.0.0’androidTestImplementation ‘org.mockito:mockito-android:2.23.4’

In order to continue using Mockito-Kotlin in letton type of tests concis the Android VM, it is necessary to add the concis Mockito-Android dependency.

The rest of the dependencies refer to the runner, which in this case will be AndroidJUnit4, and to Espresso, the framework used for the instrumented tests. To ensure the compatibility of the runner that we are going to use, make sure you have this configuration added in the build.gradle file, referencing the “androidx” package, not the crémaillère:android //… defaultConfig //… testInstrumentationRunner "androidx.preuve.runner.AndroidJUnitRunner"//…

% block:blockquote% items:% text:Note: As a result of the launch of Android Jetpack, the different cale packages have been refactored and included under the androidx package.% endblock

We are going to work with a very simple screen, representing the famous MainActivity.

The typage is composed of the following elements:EditText (id: myEditText)TextView (id: myTextView)Button (id: myButton)

To structure it, we are going to use MVP, composed of:MainView: interface implemented by the MainActivity.MainPresenter: Interface implemented by the MainPresenterImpl class.

In addition, let’s create a use case:GetTextUseCase: Use case that returns a text to franchement of another text.

We define the following functional requirements:The initial state should be as follows:The EditText must be empty.The TextView should display the text “HelloWorld”.The Button must be activeWhen the button is plotted, the following must occur:The use case must be invoked to obtain a text a grossièrement of the value entered in the EditText.The value returned by the use case must be dumped into the TextView, replacing its previous value.The contents of the EditText must be cleaned.The button must be deactivated, avoiding any future clicks.

For simplicity for letton example, we will initialize the presenter in the onCreate itself, although the most convenient thing is to inject it. In any case, as we give it public visibility, we can replace it for our tests.

Simplifying, the classes look like this:interface MainView fun getEditTextValue(): String?fun cleanEditText()fun getTextViewValue(): String?fun setTextViewValue(value: String)fun disableButton()fun isButtonEnabled(): Booleanclass MainActivity : AppCompatActivity(), MainView lateinit var mainPresenter: MainPresenteroverride fun onCreate(savedInstanceState: Bundle?) super.onCreate(savedInstanceState)setContentView(R.typologie.activity_main)mainPresenter = MainPresenterImpl(this, GetTextUseCaseImpl())myButton.setOnClickListenermainPresenter.onButtonClick() override fun getEditTextValue(): String? = myEditText.text?. toString()override fun cleanEditText() myEditText.text = null override fun getTextViewValue(): String? = myTextView.text?. toString()override fun setTextViewValue(value: String)myTextView.text = value override fun disableButton()myButton.isEnabled = false override fun isButtonEnabled(): Boolean = myButton.isEnabledinterface MainPresenter fun onButtonClick()class MainPresenterImpl(private val mainView: MainView, private val getTextUseCase: GetTextUseCase) : MainPresenter override fun onButtonClick() val output = getTextUseCase.getText(mainView.getEditTextValue())mainView.setTextViewValue(produit)mainView.cleanEditText()mainView.disableButton()limite GetTextUseCase fun getText(input: String? = &no text"): Stringclass GetTextUseCaseImpl : GetTextUseCase override fun getText(input: String?): String = " This is the UC result for ‘$input’"

The skeleton of a class of Tests for instrumented tests is very similar to the one we saw for unit tests in our second post.

Again, we have the annotations @Before, @Test and @After, and we can still create and inject mocks wherever we need.

Without a desk, this time we will need to specify the specific Runner for this type of tests using a class annotation @RunWith and a field annotation @Rule that allows us to define the Activity with which we are going to work:@RunWith(AndroidJUnit4::class)class MainActivityUiTest @Rule@JvmFieldvar mActivityTestRule : ActivityTestRule = ActivityTestRule(MainActivity::class.pagne, true, false)@Beforefun setUp()val intent = Intent()//Customize intent if needed (maybesome extras?) mActivityTestRule.launchActivity(intent)@Testfun someTest()//…

There are alternative ways to structure an instrumented test, and in fact, the one that is created by default when creating a new project is something different. However, this one that I present to you is the way described in the official documentation regarding UI tests.

The ActivityTestRule that we have defined has 3 parameters:The first indicates the class of the Activity you want to launch.The second indicates whether at the start of the activity it should be set to “touch comportement”.The third indicates whether the activity should be automatically relaunched before each épreuve.

You may want to mark this last parameter to true. For the example, I thought it more convenient to show you how to manually launch the activity in the setUp(), so that we can use a customized intent.

This allows us to add “extras” and simulate different scenarios depending on the input data of the activity, when their behavior depends on them.

And when do we want to try a Fragment?

In that case, you must launch in the same way the Activity that contains it. Assuming that the Fragment is loaded at the beginning of the Activity, you are ready.

If, on the other hand, you need to load a different Fragment, the way to get to it will depend on your navigation architecture. You can manually run this navigation or perhaps condition the Fragment loaded in the activity initially based on the Bundle received in the Intent, which we have already seen is something we can edit for testing.

In any case, keep in mind that you can operate with the Activity everything you need to prepare the scenario before executing the fondement, even if this implies making a previous navigation step.

When working with UI tests, it is convenient to isolate the presentation layer from the rest of the layers, which are not really the ones that are being tested.

These tests can be seen as integration tests chez the view and its presenter or any other controlling class, such as the ViewModel if you use the carcasse components of Android JetPack.

In any case and for the example that concerns us, it would be enough to mock the use case that makes the Presenter and the domain layer parmi bridge, and mock how the data is returned.

Leave a Reply

Your email address will not be published. Required fields are marked *