https://kotlinlang.org logo
#coroutines
Title
# coroutines
s

sjthn

06/09/2019, 5:09 PM
Need some help in understanding how to test this function:
Copy code
fun onCreate() {
    val user = profile.getUser()
    if (!user.contains("token")) {
        coroutineScope.launch(coroutineExceptionHandler) {
            launch(<http://Dispatchers.IO|Dispatchers.IO>) {
                try {
                    val token = network.fetchToken()
                    // ...
                } finally {
                    withContext(Dispatchers.Main) {
                        view.hideProgressBar()
                    }
                }
            }
        }
    } else {
        // ...
    }
}
I wrote a test function to verify if
network.fetchToken()
is called when condition is met. I wrapped it in
runBlocking
but test fails. Any ideas on how to test this?
g

gildor

06/10/2019, 2:59 AM
What kind test are you writing?
It looks untestable for unit tests for me
Instead of working with side effects, instead you should return value from function and test it
If it’s some integration test just test side effects, do not test function itself
Also this doesn’t have sense for me
Copy code
coroutineScope.launch(coroutineExceptionHandler) {
            launch(<http://Dispatchers.IO|Dispatchers.IO>) {
Just replace with
Copy code
coroutineScope.launch(coroutineExceptionHandler  + <http://Dispatchers.IO|Dispatchers.IO>) {
and not sure why you use IO there, is
fetchToken
blocking?
s

sjthn

06/10/2019, 5:21 AM
i am doing unit test.
fetchToken
is a network call
g

gildor

06/10/2019, 5:22 AM
I understand that it’s network call, but how is it implemented?
I just trying to undertstand why do you need
<http://Dispatchers.IO|Dispatchers.IO>
s

sjthn

06/10/2019, 5:23 AM
fetchToken
is a suspend function. that calls the api and returns the result synchronously
in Android, n/w call can't happen on main thread. so need a IO thread
g

gildor

06/10/2019, 5:25 AM
if it’s suspend function than it’s not syncronous, it is asyncronous by definition
Do you use retrofit 2.6 with suspend function support?
s

sjthn

06/10/2019, 5:27 AM
yes
g

gildor

06/10/2019, 5:28 AM
Than you don’t need
<http://Dispatchers.IO|Dispatchers.IO>
, Retrofit requests running on OkHttp dispatcher by default
so it’s completely fine to call it from Main thread, that is the whole point of dispatchers and coroutines, to be asyncrous and provide sequential-like way to write code
s

sjthn

06/10/2019, 5:31 AM
okay thanks. didnt know that. i thought we anyway need to call it in a non-ui thread. And now my test is working. But when I run all the tests some are failing.
I am using
Dispatchers.setMain()
and
resetMain()
in my test class
g

gildor

06/10/2019, 5:33 AM
i thought we anyway need to call it in a non-ui thread
See, suspend function is like a callback with some compile time magic, same way as with Retrofit callbacks you do not switch thread where to run it, you shouldn’t do this for suspend function if it’s not required
I run all the tests some are failing.
I don’t know what your tests are testing, so hard to say anything, try to investigate
If you just replace Dispatcher it may work for some cases, but not for cases when you do actual network call for example, which is asyncronous
I really recommend you to read official coroutine guide first
s

sjthn

06/10/2019, 5:37 AM
I went through the guide. The testing part seems difficult. I'll go through it once again. Thanks for the help.
g

gildor

06/10/2019, 5:38 AM
The testing part seems difficult
Because your function is written in a way which is very hard to test, it would be the same with any async API if you write your code this way. Essentially when you call this function you just run background job and have no way to await or know result, you just have some side effect but to check it you should wait for this job, which is not easy in this case. Of course you can replace of dispatchers for tests with Unconfined and it will work, but pretty fragile (if any of functions switches context your test will be broken)
I really don’t see how it would be different with any other async api: callbacks, rxjava, retrofit.Call etc
s

sjthn

06/10/2019, 5:49 AM
This is my test:
Copy code
@Test
fun `make api call if token is not present`() {
    `when`(profile.getUser()).thenReturn("")
    runBlocking {
        presenter.onCreate()
        verify(network).fetchToken()
    }
}
g

gildor

06/10/2019, 5:49 AM
exactly
onCreate is just do some side effect
To test it this way you should replace all dispatchers with Unconfined, because there is no other simpole way to wait for it
Instead, you can extract this to some suspend function, do request, receive result and then validate that token was requested (or even better returned together with User)
again, I don’t have your original code, hard to recommend particular solution
It’s common pattern with any asyncronous API
It’s not the only way of course, you also can observe some state to know when presenter init is finished, or something like that
s

sjthn

06/10/2019, 6:07 AM
Instead, you can extract this to some suspend function, do request, receive result and then validate that token was requested (or even better returned together with User)
But I am doing that only IIUIC. Extracted the n/w request to a suspend function that returns the token. So checking whether that token was requested i.e, the function was called or not
g

gildor

06/10/2019, 6:10 AM
yes, I just want to say that testing functions that do something asyncronous and return nothing is hard
you need sime another approach
Reaplace with Unconfined dispatcher kinda work in most cases, but it also not how it works on real app
So because of this I recommend to use some approach that will allow you to test it properly
without context I cannot propose some particular solution
s

sjthn

06/10/2019, 6:12 AM
cool. will figure out. thanks you so much
r

rook

06/10/2019, 5:49 PM
Just wanted to point out, if the side effects result in a call to an object with an interface, you can check that the function was called with a mocked version of the interface (like your
view
, for example)
s

sjthn

06/11/2019, 8:03 AM
@rook yes that's what i am doing. That works fine if it's not in coroutine.
4 Views