Testing Retrofit + Kotlin Coroutines

Kotlin Coroutines are a great and powerful new language feature. It lets you write asynchronous code like synchronous code, which reduces complexity and improves readability a lot! Motivated by Svetlana Isakovas talk at this year’s Droidcon Berlin we decided to use Coroutines instead of RxJava for our Retrofit code.* The result was very impressive, we got a leaner and much more readable code base.

//Example: fetch data async without using callbacks or subscribers
val myRemoteData = myRESTService.fetchRemoteData().await()

However, we faced some challenges testing Retrofit with Kotlin Coroutines. The biggest challenge was the lack of documentation on this. This article describes our approach and experiences.**

Retrofit + Coroutines

Retrofit is a very popular REST library for Java and Android. It features asynchronism right out-of-the-box. Unfortunately Coroutines aren’t featured officially yet. However, there is a very nice third party library***, which adds Coroutines support to Retrofit.

So instead of this…

service.listReposWithRx(user = "a11n").subscribe { 
repos ->
//do whatever you want with repos

}

…our code becomes this…

val repos = service.listReposWithCoroutines(user = "a11n").await()
//do whatever you want with repos

Testing

In order to test Retrofit with Kotlin Coroutines we’ve done three things:

  1. Mock the Retrofit service interface with Mockito…
@Mock
lateinit var service: GitHubService

@BeforeEach
internal fun setUp() {
MockitoAnnotations.initMocks(this)
}

2. …return a fake Call instance…

    whenever(service.listReposWithCoroutines(any())).
thenReturn(Calls.response(expectedRepos))

The retrofit-mock artifact provides some very convenient factory methods to create Call instances which immediately respond or fail. These methods could perfectly be used for returning fake Call instances.

Special thanks to Jake Wharton for pointing this out 😊🍻

3. …wrap the Coroutine call in runBlocking…

@Test
internal fun should_doSomethingWithRemoteDataFetchedWithCoroutines() {
val actualRepos = runBlocking {
service.listReposWithCoroutines("a11n").await() }

actualRepos shouldEqual expectedRepos
}

Usually, Coroutines are used within suspension functions. Since we want the test function to call the Coroutine immediately we need wrap the call in runBlocking{}.

… that’s it.

Example

You can find the complete example at GitHub.

Please note, the example contains a very simplified use case for the purpose of brevity and demonstration. In your production code you will most probably not test the Retrofit service itself. But you will need to inject a mocked Retrofit service into a business logic class. Hopefully the presented approach saves you some time.

Credits

Thanks to my colleagues Andreas Ruitter and Tobias Suhrborg for reviewing and discussion.

Remarks

*Please note that Coroutines are still experimental before using it in your production code. However, Svetlana Isakova from JetBrains mentioned that the Coroutines API is pretty close to final and most changes will happen under the hood.

**Please let me know in the comments how you are using Retrofit with Coroutines and how you test it. Really looking forward to it. 🤔

***Update: As you might want to follow in this discussion Jake Wharton published a dedicated Retrofit Kotlin Coroutine adapter which allows returning of Deffered. This is a better alternative to the library used when this article was created. The adapter approach might get part of Retrofit directly when Coroutines mature from experimental to stable. So you might want to use this approach in your applications.