Justin Hammenga
Justin Hammenga

Reputation: 1091

Clean Coroutines usage in Kotlin with Unit Test support

Since a while we're working with Kotlin and one of the things we're currently focussing on is using Coroutines to take care of operations we want to run async.

While the example usages are clear and that works, I'm having some issues integrating this in a clean manner within our architecture. When looking at a method's implementation for a domain-focussed class, the idea is that it's easy to read and there is as less "noise" as possible from async functionality. I know I can't have async, without actually using it. So writing something like this is what I'd like:

val data = someService.getData().await()
// work with data

But this is what I'd like to prevent:

launch(UI) {
  val data 
  val job = async(CommonPool) {
    data = someService.getData()
  }

  job.await()
  // work with data 
}

That, I'd like paired with practical Unit Tests for these domain-focussed classes, but I can't really get that to work. Let's look at an example:

// Some dependency doing heavy work
class ApiClient {
    suspend fun doExpensiveOperation(): String {
        delay(1000)

        return "Expensive Result Set"
    }
}

// Presenter Class
class Presenter(private val apiClient: ApiClient,
                private val view: TextView) {

    private lateinit var data: String

    fun start() {
        log("Starting Presenter")
        runBlocking {
            log("Fetching necessary data")
            data = apiClient.doExpensiveOperation()
            log("Received necessary data")
        }

        workWithData()

        log("Started Presenter")
    }

    fun workWithData() {
        log(data)
    }

    private fun log(text: String) {
        view.append(text+"\n")
    }
}

// In an Activity
val presenter = Presenter(ApiClient(), someTextView)
presenter.start()

That works (screenshot: https://i.sstatic.net/v0xDB.jpg). Now lets look at the test.

class PresenterTest {
    // ... Declared fields

    @Before
    fun setUp() {
        // Init mocks (apiClient, textView)
        MockitoAnnotations.initMocks(this)

        // Set mock responses
        runBlocking {
            given(apiClient.doExpensiveOperation()).willReturn("Some Value")
        }

        presenter = Presenter(apiClient, textView)
    }

    @Test
    @Throws(Exception::class)
    fun testThat_whenPresenterStarts_expectedResultShows() {
        // When
        presenter.start()

        // Then
        Mockito.verify(textView).text = "Some Value\n"
    }
}

Now this test is less than ideal, but regardless, it never even gets to the point where it can verify things work as intended, because lateinit var data wasn't initialized. Now ultimately the aesthetics and readability of our domain classes is simply how far I want to go, which I have some practical working examples for that I'm happy with. But making my tests work seems to be challenging.

Now there's some different write-ups online about this kind of stuff, but nothing has really worked out for me. This (https://medium.com/@tonyowen/android-kotlin-coroutines-unit-test-16e984ba35b4) seems interesting, but I don't like the idea of a calling class launching a context for a presenter, because that in turn has a dependency that does some async work. Although as an abstract thought I like the idea of "Hey presenter, whatever you do, report back to me on a UI context", it rather feels as a fix to make things work, leading to a shared concern for async functionality across different objects.

Anyway, my question: Moving away from the short examples, does anyone have any pointers on how to integrate coroutines within a bigger architecture, with working unit tests? I'm also very open to arguments that make me alter my way of viewing things, given that's it's convincing on a different level than "If you want things to work, you have to sacrifice.". This question goes beyond just making the example work, as that is just an isolated example, while I'm looking for a real solid integration within a big project.

Looking forward to your input. Thanks in advance.

Upvotes: 2

Views: 1123

Answers (2)

Jim Morris
Jim Morris

Reputation: 1

You should abandon coroutines and use RxJava instead. There you will find the kind of conciseness and simplicity you seek. When I ask most developers why they use coroutines, their answer is always the same: "Well, coroutines are the new, new thing, and we should use the latest technology from Google". Except that coroutines are not new. They were first introduced in about 1952 (See "Coroutines" in Wikipedia) as a proposal for doing asynchronous software development. It is pretty clear that the Computer Science community rejected coroutines years ago as not being the best approach for asynchronous programming. Why JetBrains decided to introduce an old, rejected technology into Kotlin is something you will have to ask JetBrains. I have had to deal with coroutines in code that others have written for several years now, and I always find coroutines to be needlessly complex. There is no way that coroutines do anything more than decrease maintainability when maintenance developers have to deal with coroutine spaghetti written by a developer who has long since departed the project.

The next thing I hear from these same developers is that RxJava is old technology and coroutines are new technology. If they had done their research, they would never have made such an outrageously incorrect statement. IMHO, RxJava is the most important new development in asynchronous software development in the entire history of computer science.

Upvotes: -1

Lukas1
Lukas1

Reputation: 582

I'd suggest an approach of having some kind of AsyncRunner interface and have two implementations of this AsyncRunner interface. One would be implementation for Android, using launch(UI), and the other would be some blocking implementation, using runBlocking.

Passing the right type of AsyncRunner into code run within app and code run in unit test should be done by dependency injection. In your code then, you'd not use coroutines directly, instead you'd use injected AsyncRunner to run asynchronous code.

Example implementations of this AsyncRunner might look like this:

interface AsyncRunner {
    fun <T>runAsync(task: () -> T, completion: (T) -> Unit)
}

class AndroidCoroutineAsyncRunner: AsyncRunner {
    override fun <T>runAsync(task: () -> T, completion: (T) -> Unit) {
        launch(UI) {
            completion(async(CommonPool) { task() }.await())
        }
    }
}

class BlockingCoroutineAsyncRunner: AsyncRunner {
    override fun <T>runAsync(task: () -> T, completion: (T) -> Unit) {
        runBlocking {
            completion(async(CommonPool) { task() }.await())
        }
    }
}

where the task parameter represents the thread blocking code (for example fetching data from API) and completion parameter will get data from the task and do something with them.

Upvotes: 1

Related Questions