The Google I/O event in May 2017 announced official Kotlin support. From that moment, Kotlin popularity skyrocketed among Android developers. Keeping in mind the current trends and considering Google’s announcements about shifting the Android toward Kotlin, which is reflected in the Android documentation and the code examples, we can assume that in two to three years, Kotlin will displace Java.
Figure 3-1 shows Java vs. Kotlin usage prediction, which indicates that Kotlin will soon overtake Java in the Android development world.
This chapter explains how to migrate existing Espresso Java tests to Kotlin, lists the possible benefits of writing UI tests in Kotlin, and provides an example of creating Espresso DSL with practical examples and tasks.
Migrating Espresso Java Tests to Kotlin
Kotlin works side-by-side with Java on Android, meaning that you can add Kotlin code to your existing projects and can call Java code from Kotlin and vice versa.
The first step is to tell the Android Studio IDE that the project uses Kotlin by adding the kotlin-gradle-plugin dependency to the project build.gradle file, as shown:
After the project is synched, you can start converting Java classes to Kotlin. This can easily be achieved by selecting a Java file or a package, opening the Code menu, and choosing the Convert Java File to Kotlin File option. You can also right-click the file or package and select this option from the pop-up menu (see Figure 3-2).
Things can look simple for the test classes files, but can be complicated for complex ViewActions or ViewMatchers. When the IDE convertor can’t handle the code complexity, it will require developer interaction. The dialog box in Figure 3-3 alerts the developer to this fact.
You can also paste existing Java code into a Kotlin file. In this case, the IDE will identify that the code in the clipboard was copied from a Java file and will suggest converting it to Kotlin code, as shown in Figure 3-4.
You will be asked to add new imports to the Kotlin file if they are not present, as shown in Figure 3-5.
The conversion cannot handle methods with multiple imports. This requires manual interaction from the developer as well (see Figure 3-6).
The following shows an example of an Espresso UI test method conversion from Java to Kotlin. As you may notice, there is almost no difference except for the function declaration and semicolons at the end of the lines.
Adding a New TO-DO Test in the Java and Kotlin Languages, Respectively.
You can see more examples of converting Java files to Kotlin—based on the examples implemented in the ViewActionsTest.kt, RecyclerViewActionsTest.kt, and DataInteractionsTest.kt classes—in the chapter3/testsamples package.
Exercise 10
Converting Java Code to Kotlin
1.
Convert an existing Java file to Kotlin.
2.
Convert a package containing multiple Java files to Kotlin.
3.
Copy a Java code sample and paste it into a Kotlin file. See what happens if you paste only half of the Java method. Will the conversion be correct?
Benefits of Writing Tests in Kotlin
Bringing Kotlin into your test codebase has many advantages. Among them are these:
Function as a type support
Extension functions
String templates
Ability to import R.class resources
Much cleaner code
Function as a Type
This process saves a function in a variable and then uses it as another function argument or returns a function by another function. In the following example, you can see how the Espresso ViewMatchers.withText() function is returned as a value of the viewWithText() function:
fun viewWithText(text: String): ViewInteraction =
Espresso.onView(ViewMatchers.withText(text))
Extension Functions
Extensions do not actually modify the classes they extend. By defining an extension, you do not add new members into a class, but only make new functions callable with the dot-notation on instances of this type. With the help of extension functions, the Espresso perform(ViewAction.typeText()) function can be represented in the following way:
fun ViewInteraction.type(text: String): ViewInteraction =
perform(ViewActions.typeText(text))
In this example, we extended the ViewInteraction class with an additional type() method.
String Templates
Strings may contain template expressions, i.e. pieces of code that are evaluated and whose results are concatenated into the string. A template expression starts with a dollar sign ($) and contains a simple name. Take a look at this example:
fun main(args: Array<String>) {
val i = 10
println("i = $i") // prints "i = 10"
}
Or consider an arbitrary expression in curly braces:
fun main(args: Array<String>) {
val s = "abc"
println("$s.length is ${s.length}") // prints "abc.length is 3"
}
Import R.class Resources
Kotlin—together with the Kotlin Android Gradle plugin—simplifies the way that project resources (including string values, IDs, and drawables) can be accessed. In the following listing, based on the addsNewToDo() test implementation from the chapter3/testsamples/ViewActionsKotlinTest.kt file, you can see how Kotlin allows us to import application resources.
Instead of the whole R.class, Android Studio IDE allows you to import only one or several resources (see Figure 3-7).
Espresso Domain-Specific Language in Kotlin
With the help of the Kotlin extension functions and function as a type support, we can drastically reduce the boilerplate of the test code by implementing Espresso domain-specific language (DSL). The goal of our Espresso DSL is to simplify our test codebase, make it more legible and, most importantly, make our tests easy to write and maintain.
First, we must determine which Espresso functions or expressions we use the most in our UI test codebase:
View or data interactions represented by the Espresso.onView() and Espresso.onData() methods—The starting point of every line of Espresso test code.
Different view actions, like ViewActions.click(), ViewActions.typeText(), ViewActions.swipeDown(), ViewActions.closeSoftKeyboard(), etc.
Plenty of view matchers, which are the most used functions inside the test codebase, since they are used not only to locate elements on the page but also in combination with view assertions check view properties: ViewMatchers.withId(), ViewMatchers.withText(), check(matches(ViewMatchers.isDisplayed())), and so on.
Aggregated Hamcrest matchers like Matchers.allOf() or Matchers.anyOf().
Recycler view actions such as RecyclerViewActions.scrollToHolder() and RecyclerViewActions.actionOnItem().
Of course, this list can be extended or reduced based on your needs. It is worth it to highlight that the aim of this paragraph is not to standardize the Espresso DSL with Kotlin, but to provide an example of how it can be done, so that you can apply it to your test projects.
The core Espresso.onView() and Espresso.onData() methods are the first functions we going to work with. Seeing that they always take a parameter view matcher or object matcher, we can convert the whole expression into one single Kotlin function, as follows:
fun viewWithText(text: String): ViewInteraction = Espresso.onView(ViewMatchers.withText(text))
Or in case of onData():
fun onAnyData(): DataInteraction = Espresso.onData(CoreMatchers.anything())
You may notice that the returning types are identical to those returned by the onView() and onData() methods—ViewInteraction and DataInteraction, respectively. Another thing is that it is possible to pass a parameter into the extension function that’s used inside the original one. These examples are using Kotlin local functions (i.e., a function inside another function) to simplify the code and can be represented by the following more complex function declarations:
You already know that the onView() method returns a ViewInteraction type containing the perform() public method. Now we are going to declare another function that will replace perform(ViewActions.click()). In order to keep the dot notation for the ViewInteraction class, we are going to extend it with our new function, as follows:
fun ViewInteraction.click(): ViewInteraction = perform(ViewActions.click())
This way, we represent the perform(ViewActions.click()) expression by a simple click() function. This example, using the view with text, looks this way now:
viewWithText("item 1").click()
Here we also keep the right return ViewInteraction type. It’s the same one that is returned by the original perform() method.
The same extension function can be added to the DataInteraction class. The only thing we need to do is replace the ViewInteraction extension class with DataInteraction:
fun DataInteraction.click(): ViewInteraction = perform(ViewActions.click())
That is it. Looking good so far.
Moving forward to view matchers and view assertions where the same approach with extension functions is used. Here is an example of an assertion of a view being displayed:
Having DSL samples of the Espresso.onView(), ViewActions, and ViewAssertions methods allows us to compare one of the commonly used raw Espresso expressions with one written in DSL (also assuming that we imported all the Espresso static methods):
Next, we have the recycler view actions. Similar to the previous examples, we can handle recycler view actions. The following example is based on RecyclerViewActions.actionOnItemAtPosition() and looks the following way:
These examples and even more are defined in the chapter3/EspressoDsl.kt file of our sample project for your reference.
Now it is time to apply our domain specific language to our tests and observe how converted Espresso Kotlin tests look compared to those written using DSL. First let’s look at the ViewActions tests samples implemented in ViewActionsKotlinTest.kt.
The checksToDoStateChange() Test Method Implemented in chapter3.testsamples.ViewActionsKotlinTest.kt.
As you may notice, the test method implemented with DSL is much more legible and clean. For even more readability, we declared all used view interactions at the beginning of the test class. This makes the tests even smoother.
Exercise 11
Practicing Espresso DSL Usage
1.
Look through the tests implemented in the DataInteractionKotlinDslTest.kt and RecyclerViewActionsKotlinDslTest.kt classes and understand how DSL was applied to these tests.
2.
Based on the editsToDo() test method from ViewActionsKotlinTest.kt, finish implementation of the editsToDoDsl() test case located in ViewActionsKotlinDslTest.kt using DSL.
Summary
After many years of the Java language dominating the Android platform, Kotlin brings a fresh and progressive approach to its applications and to test development. Tests written in Kotlin are more legible, cleaner, and easier to maintain. Its extension functions support allows developers to easily create and test domain-specific language, which simplifies the test code even more. Migration from Java to Kotlin is painless and fast. In the end, it is clear that at some point Kotlin will replace Java in Android application development. You should be prepared to at least migrate to Kotlin and improve your test code.