Unit test on Kotlin Extension Function on Android SDK Classes - android

Kotlin extension function is great. But how could I perform unit test on them? Especially those that is of Android SDK provided class (e.g. Context, Dialog).
I provide two examples below, and if anyone could share how I could unit test them, or if I need to write them differently if I really want to unit test them.
fun Context.getColorById(colorId: Int): Int {
if (Build.VERSION.SDK_INT >= 23)
return ContextCompat.getColor(this, colorId)
else return resources.getColor(colorId)
}
and
fun Dialog.setupErrorDialog(body : String, onOkFunc: () -> Unit = {}): Dialog {
window.requestFeature(Window.FEATURE_NO_TITLE)
this.setContentView(R.layout.dialog_error_layout)
(findViewById(R.id.txt_body) as TextView).text = body
(findViewById(R.id.txt_header) as TextView).text = context.getString(R.string.dialog_title_error)
(findViewById(R.id.txt_okay)).setOnClickListener{
onOkFunc()
dismiss()
}
return this
}
Any suggestion would help. Thanks!

The way I'm testing extension functions on Android classes at the moment is by mocking the Android class. I know, this is not an optimal solution as it mocks the class under test and requires certain knowledge about how the function works (as it is always the case when mocking), but as extension functions are internally implemented as static functions I guess it's acceptable until someone comes up with something better.
As an example consider the JsonArray class. We've defined an extension function for receiving the last item's index:
fun JSONArray.lastIndex() = length() - 1
The according test (using the Spek test framework and mockito-kotlin) looks like this.
#RunWith(JUnitPlatform::class)
object JsonExtensionTestSpec : Spek({
given("a JSON array with three entries") {
val jsonArray = mock<JSONArray> {
on { length() } doReturn 3
}
on("getting the index of the last item") {
val lastIndex = jsonArray.lastIndex()
it("should be 2") {
lastIndex shouldBe 2
}
}
}
given("a JSON array with no entries") {
val jsonArray = mock<JSONArray>({
on { length() } doReturn 0
})
on("getting the index of the last item") {
val lastIndex = jsonArray.lastIndex()
it("should be -1") {
lastIndex shouldBe -1
}
}
}
})
The difficulty with your functions is, that they also use Android classes internally. Unfortunately I don't have a solution for this right now.

Related

What is the purpose of kotlin contract

Was reading the apply function code source and found
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
and contract has an empty body, experimental
#ContractsDsl
#ExperimentalContracts
#InlineOnly
#SinceKotlin("1.3")
#Suppress("UNUSED_PARAMETER")
public inline fun contract(builder: ContractBuilder.() -> Unit) { }
what is the real purpose of contract and is it here to stay in the next versions?
What is the real purpose of contract
The real purpose of Kotlin contracts is to help the compiler to make some assumptions which can't be made by itself. Sometimes the developer knows more than the compiler about the usage of a certain feature and that particular usage can be taught to the compiler.
I'll make an example with callsInPlace since you mentioned it.
Imagine to have the following function:
fun executeOnce(block: () -> Unit) {
block()
}
And invoke it in this way:
fun caller() {
val value: String
executeOnce {
// It doesn't compile since the compiler doesn't know that the lambda
// will be executed once and the reassignment of a val is forbidden.
value = "dummy-string"
}
}
Here Kotlin contracts come in help. You can use callsInPlace to teach the compiler about how many times that lambda will be invoked.
#OptIn(ExperimentalContracts::class)
fun executeOnce(block: ()-> Unit) {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
}
#OptIn(ExperimentalContracts::class)
fun caller() {
val value: String
executeOnce {
// Compiles since the val will be assigned once.
value = "dummy-string"
}
}
is it here to stay in the next versions?
Who knows. They are still experimental after one year, which is normal for a major feature. You can't be 100% sure they will be out of experimental, but since they are useful and they are here since one year, in my opinion, likely they'll go out of experimental.

JUnit 5 - Parameterized Nested Tests

Expected
Creating a nested test within a parameterized test in JUnit5.
There are many conditions for the Android ViewModel using param. tests. I want to organize the tests within the param. test to improve output readability.
#ExtendWith(InstantExecutorExtension::class)
class ContentViewModelTest {
private fun `FeedLoad`() = Stream.of(
FeedLoadTest(isRealtime = false, feedType = MAIN, timeframe = DAY, lceState = LOADING),
FeedLoadTest(isRealtime = false, feedType = MAIN, timeframe = DAY, lceState = CONTENT))
#ParameterizedTest
#MethodSource("FeedLoad")
fun `Feed Load`(test: FeedLoadTest) {
#Nested
class FeedLoadNestedTest {
#Test
fun `all fields are included`() {
assertThat(4).isEqualTo(2 + 2)
}
#Test
fun `limit parameter`() {
assertThat(4).isEqualTo(3 + 2)
}
}
...
}
data class FeedLoadTest(val isRealtime: Boolean, val feedType: FeedType,
val timeframe: Timeframe, val lceState: LCE_STATE)
}
Observed
The normal parameterized assertions [not depicted] work as expected. The nested FeedLoadNestedTest does not run within the Stream of parameterized FeedLoad tests.
#Sam Brannen, thanks for the feedback!
Sam has indicated on GitHub, #Nested annotation on local classes will not be a viable option.
We have no plans to support #Nested on local classes defined within the scope of a method (function in Kotlin).
Solution
Implement multiple parameterized tests that pass in the same stream.
This will allow for assertions and logic to be organized into separate parameterized functions while testing the same data passed in via the stream.
#ParameterizedTest
#MethodSource("FeedLoadStream")
fun `Feed Load Part One`(test: FeedLoadTest) {
...
}
#ParameterizedTest
#MethodSource("FeedLoadStream")
fun `Feed Load Part Two`(test: FeedLoadTest) {
...
}
#ParameterizedTest
#MethodSource("FeedLoadStream")
fun `Feed Load Part Three`(test: FeedLoadTest) {
...
}

How to zip two Observables in Android?

In my app I have two services and both of them have a method that makes a requests and then returns an Observable of different type.
I want to display in a RecyclerView a list composed of the result of combining these two Observables. I googled about this and found the zip() method that seems to do exactly what I want. I'm trying to implement it but I don't know how to do it correctly.
While I was googling, I came up with this this article which seems to explain it clearly. Even though the author is using Singles while I am using Observables.
As far as I understand how zip() works, I know I have to pass every Observable I want to "zip" and then I must specify a function that will compose my final Observable, right?
This is my code so far:
interface InvitationService {
#GET("foo/{userId}")
fun getFooByUser(#Path("userId") userId: String): Observable<Response<ArrayList<Foo>>>
}
interface InvitationService {
#GET("bar/{userId}")
fun getBarByUser(#Path("userId") userId: String): Observable<Response<ArrayList<Bar>>>
}
class FooRemoteDataSource : FooDataSource {
private var apiService: FooService
fun getFooByUser(userId:String) {
return apiService.getFooByUser(userId)
}
}
class BarRemoteDataSource : BarDataSource {
private var apiService: BarService
fun getBarByUser(userId:String) {
return apiService.getBarByUser(userId)
}
}
class FooRepository(private val remoteDataSource: InvitationRemoteDataSource) : FooDataSource {
override fun getFooByUser(userId: String): Observable<Response<ArrayList<Foo>>> {
return remoteDataSource.getFooByUser(userId)
}
}
class BarRepository(private val remoteDataSource: BarRemoteDataSource) : BarDataSource {
override fun getBarByUser(userId: String): Observable<Response<ArrayList<Bar>>> {
return remoteDataSource.getBarByUser(userId)
}
}
And here is where I'm actually stuck:
class ListPresenter(var listFragment: ListContract.View?,
val fooRepository: FooRepository,
val barRepository: BarRepository) : ListContract.Presenter {
fun start() {
loadList()
}
private fun loadLists() {
//HERE IS WHERE IM STUCK
Observable.zip(fooRepository.getFooByUser(userId).subscribeOn(Schedulers.io()),
barRepository.getBarByUser(userId).subscribeOn(Schedulers.io()),
)
// AFTER 'ZIPPING' THE OBSERVABLES
// I NEED TO UPDATE THE VIEW ACCORDINGLY
}
}
I don't know how to call zip() properly, I know that I must pass a function but I don't get it because in the article linked above the author is using a Function3 because he has 3 Observables.
As I only have 2, I don't know how to do it. If open curly braces after a comma inside the method args, it requires me to return a BiFunction<ArrayList<Foo>, ArrayList<Bar>> which is what I don't know how to specify.
Would someone explain it to me?
For Kotlin you should use RxKotlin rather than RxJava. BiFunction, Function3 come from RxJava. With RxKotlin you can use lambdas instead.
As far as I understand how zip() works, I know I have to pass every Observable I want to "zip" and then I must specify a function that will compose my final Observable, right?
Correct, and here is a minimal example, which demonstrates how to do it.
Example 1
val observable1 = listOf(1, 2, 3).toObservable()
val observable2 = listOf(4, 5, 6).toObservable()
val zipped = Observables.zip(observable1, observable2) { o1, o2 -> o1 * o2}
In this example you have two observables, each emitting integers. You pass them to zip and as third argument a lambda which defines a way to "cobmine them". In this case it multiplies them.
The resulting observable zipped will emit: 4, 10 and 18.
Example 2
Here another example zipping three observables which are not all of the same type:
val obs1 = listOf("on", "tw", "thre").toObservable()
val obs2 = listOf("n", "o", "e").toObservable()
val obs3 = listOf(1, 2, 3).toObservable()
val zipped = Observables.zip(obs1, obs2, obs3) { o1, o2, o3 ->
"$o1$o2 = $o3"
}
Here, each element of the resulting observable will be a string: "one = 1", "two = 2", "three = 3"
Zipping two Observables of different types using BiFunction
override fun getCommoditiesAndAddresses() {
view.showProgress()
view.hideViews()
Observable.zip(Commo24Retrofit.createAuthService(RateAPIService::class.java)
.getCommodities(),
Commo24Retrofit.createAuthService(RateAPIService::class.java)
.getLocations(GetLocationsRequest(getOrgId())),
BiFunction { commodityResponse: GetCommoditiesResponse, locationsResponse: GetLocationsResponse ->
handleCommoditiesAndAddresses(commodityResponse, locationsResponse)
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
view.hideProgress()
view.showViews()
view.handleCommodities(it?.commodities)
view.handleLocations(it?.locations)
}, { throwable ->
view.hideProgress()
view.handleFailure(throwable.getErrorMessage(context))
})
}
Look, how I'm handling the response:
private fun handleCommoditiesAndAddresses(commodityResponse: GetCommoditiesResponse, locationsResponse: GetLocationsResponse): CommoditiesAddresses {
return CommoditiesAddresses(commodityResponse.commodityList, locationsResponse.addressList)
}
Here, check the API Service:
interface RateAPIService {
#POST("get-org-address")
fun getLocations(#Body getLocationsRequest: GetLocationsRequest): Observable<GetLocationsResponse>
#POST("get-commodity-list")
fun getCommodities(): Observable<GetCommoditiesResponse>
}
If you have any doubt you can comment it out.

Kotlin and Android Espresso tests: Adding extension function with receiver

I'm still working on improving my understanding of extension functions with receivers and need some help of you experts on a question I have regarding this.
I have an Android Espresso testcase where I check that I have selected the items of a recyclerview. This is the same code repeated many times. I was wondering if it would be possible using kotlins extension functions with receiver to simplify this.
My test code now:
#Test
public void shouldSelectAll() {
...
onView(withRecyclerView(R.id.multiselectview_recycler_view).atPosition(0))
.check(RecyclerViewMatcher.isSelected(true));
onView(withRecyclerView(R.id.multiselectview_recycler_view).atPosition(1))
.check(RecyclerViewMatcher.isSelected(true));
onView(withRecyclerView(R.id.multiselectview_recycler_view).atPosition(2))
.check(RecyclerViewMatcher.isSelected(true));
}
Is it some how possible to create a function atPositions(varag positions: Int) that would take an integer array and call the assertion on each of the positions in the array. Like this:
#Test
public void shouldSelectAll() {
...
onView(withRecyclerView(R.id.multiselectview_recycler_view).atPositions(0, 1, 2))
.check(RecyclerViewMatcher.isSelected(true));
}
Sure!
private fun Int.matchAsRecyclerView(): RecyclerViewMatcher = withRecyclerView(this)
private fun RecyclerViewMatcher.checkAtPositions(vararg indices: Int, assertionForIndex: (Int) -> ViewAssertion) {
for(index in indices) {
onView(this.atPosition(index)).let { viewMatcher ->
viewMatcher.check(assertionForIndex(index))
}
}
}
Which should work as
R.id.multiselectview_recycler_view.matchAsRecyclerView().checkAtPositions(0, 1, 2, assertionForIndex = {
index -> RecyclerViewMatcher.isSelected(true)
})

Unit testing coroutines on UI thread

I'm using coroutines to do an asynchronous call on pull to refresh like so:
class DataFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener {
// other functions here
override fun onRefresh() {
loadDataAsync()
}
private fun loadDataAsync() = async(UI) {
swipeRefreshLayout?.isRefreshing = true
progressLayout?.showContent()
val data = async(CommonPool) {
service?.getData() // suspending function
}.await()
when {
data == null -> showError()
data.isEmpty() -> progressLayout?.showEmpty(null, parentActivity?.getString(R.string.no_data), null)
else -> {
dataAdapter?.updateData(data)
dataAdapter?.notifyDataSetChanged()
progressLayout?.showContent()
}
}
swipeRefreshLayout?.isRefreshing = false
}
}
Everything here works fine when I actually put it on a device. My error, empty, and data states are all handled well and the performance is good. However, I'm also trying to unit test it with Spek. My Spek test looks like this:
#RunWith(JUnitPlatform::class)
class DataFragmentTest : Spek({
describe("The DataFragment") {
var uut: DataFragment? = null
beforeEachTest {
uut = DataFragment()
}
// test other functions
describe("when onRefresh") {
beforeEachTest {
uut?.swipeRefreshLayout = mock()
uut?.onRefresh()
}
it("sets swipeRefreshLayout.isRefreshing to true") {
verify(uut?.swipeRefreshLayout)?.isRefreshing = true // says no interaction with mock
}
}
}
}
The test is failing because it says that there was no interaction with the uut?.swipeRefreshLayout mock. After some experimenting, it seems this is because I'm using the UI context via async(UI). If I make it just be a regular async, I can get the test to pass but then the app crashes because I'm modifying views outside of the UI thread.
Any ideas why this might be occurring? Also, if anyone has any better suggestions for doing this which will make it more testable, I'm all ears.
Thanks.
EDIT: Forgot to mention that I also tried wrapping the verify and the uut?.onRefresh() in a runBlocking, but I still had no success.
If you want to make things clean and consider using MVP architecture in the future you should understand that CourutineContext is external dependency, that should be injected via DI, or passed to your presenter. More details on topic.
The answer for your question is simple, you should use only Unconfined CourutineContext for your tests. (more)
To make things simple create an object e.g. Injection with:
package com.example
object Injection {
val uiContext : CourutineContext = UI
val bgContext : CourutineContext = CommonPool
}
and in test package create absolutely the same object but change to:
package com.example
object Injection {
val uiContext : CourutineContext = Unconfined
val bgContext : CourutineContext = Unconfined
}
and inside your class it will be something like:
val data = async(Injection.bgContext) {service?.getData()}.await()

Categories

Resources