https://kotlinlang.org logo
#multiplatform
Title
# multiplatform
b

bsimmons

03/02/2021, 4:55 PM
What is the recommend way to use
runBlocking
for unit tests in a MPP for native/ios? It looks like kotlinx-coroutines-core:1.4.1 doesn't include
runBlocking
for native.
j

Javier

03/02/2021, 4:58 PM
I thought
runBlocking
exists for all except JS
Indeed in my lib I have it
c

Casey Brooks

03/02/2021, 5:00 PM
It won’t be accessible in the
commonTest
sourceset, because it’s not available on all platforms. But you can use it in
iosTest
, or actual/expect it manually to be used in
commonTest
1
b

bsimmons

03/02/2021, 5:03 PM
Yeah, I am trying to implement it as an
actual
in
iosMain
but I'm getting this weird compilation error when I use it there,
I guess version
1.4.1
is conflicting with Ktor which is using
1.4.2-native-mt
. Does that mean I am forced to use the native-mt version?
j

John O'Reilly

03/02/2021, 5:27 PM
I think the general expectation when using KMP is that you'll need to use
native-mt
version....there's several libraries now that depend on that
j

Javier

03/02/2021, 5:28 PM
I have a doubt, why the coroutines native artifact is not, by default, the native-mt?
it will be merged some day?
j

John O'Reilly

03/02/2021, 5:30 PM
it's hard to say.....complicated somewhat I think by statement by Jetbrains that they're working on replacement https://blog.jetbrains.com/kotlin/2020/07/kotlin-native-memory-management-roadmap/
👀 1
b

bsimmons

03/02/2021, 5:35 PM
@John O'Reilly Hmmm, does that mean that I need to completely rewrite code to avoid the whole frozen mutability exception business?
j

John O'Reilly

03/02/2021, 5:36 PM
I think most of the changes will be under the hood....but also, from that article "We plan to introduce it in a way that is mostly compatible with existing code, so code that is currently working will continue to work. Namely, we plan to continue to support object freezing as a safety mechanism for race-free data sharing"
b

bsimmons

03/02/2021, 5:41 PM
Ok. But for now we'll have to deal with object freezing if we want to use Kotlin 1.4 and Ktor?
j

John O'Reilly

03/02/2021, 5:45 PM
well, it depends I think on your threading/data modification setup.....in basic case where you're for example making calls in to shared code from main thread and calling say "main-safe" ktor api calls and not mutating data coming back (which should be pretty common case) then my understanding at least is that you shouldn't have to worry about explicitly freezing objects
b

bsimmons

03/02/2021, 5:52 PM
Yeah, unfortunately, a lot of my code does modify stuff from suspend functions. To do things like caching and such. I guess I'll have to find a thread-safe way to do this.
c

CLOVIS

03/02/2021, 6:21 PM
For caches, you probably already use some kind of lock to edit the cache (because otherwise your cache would get destroyed?) In that case, your code is probably fine
b

bsimmons

03/02/2021, 6:27 PM
Yeah, I am already using a lock to keep the cache consistent, but now it is also getting frozen, so I don't get a chance to use the lock in the first place.
c

CLOVIS

03/02/2021, 8:58 PM
Instead of using a lock, you could designate a thread as the ‘owner' of the cache (taking the lock == being the one running on that thread), and now you don't need to freeze anymore right? Since the cache can only be accessed by one thread
b

bsimmons

03/02/2021, 9:20 PM
Yes, definitely possible and a good idea. Just a lot of updating just to use 1.4. I guess you can't win them all. 😉
j

Joost Klitsie

03/03/2021, 12:31 PM
If it is a unit test, you should be able to work around this issue by using a coroutine helper class. This one you can override during test cases to use Dispatchers.Unconfined and then everything will just run concurrently and you do not need runBlocking
We use something like that:
Copy code
open class CoroutineHelper @Inject constructor() {
	open val mainScope: CoroutineScope by lazy { CoroutineScope(Dispatchers.Main) }
	open val mainContext: CoroutineContext by lazy { Dispatchers.Main }
	open val mainImmediateScope: CoroutineScope by lazy { CoroutineScope(Dispatchers.Main.immediate) }
	open val ioScope: CoroutineScope by lazy { CoroutineScope(<http://Dispatchers.IO|Dispatchers.IO>) }
	open val defaultScope: CoroutineScope by lazy { CoroutineScope(Dispatchers.Default) }
	open val ioContext: CoroutineContext by lazy { <http://Dispatchers.IO|Dispatchers.IO> }
	open val defaultContext: CoroutineContext by lazy { Dispatchers.Default }
	open val unconfinedScope: CoroutineScope by lazy { CoroutineScope(Dispatchers.Unconfined) }
}
then in the testcases you can override whatever you want:
Copy code
class TestCoroutineHelper : CoroutineHelper() {
	override val mainScope: CoroutineScope
		get() = CoroutineScope(Dispatchers.Unconfined)
	override val mainContext: CoroutineContext
		get() = Dispatchers.Unconfined
	override val ioContext: CoroutineContext
		get() = Dispatchers.Unconfined
}
So a test setup looks like:
Copy code
private val coroutineHelper = TestCoroutineHelper()

	private lateinit var presenter: BatteryStatusPresenter

	override fun setUp() {
		super.setUp()
		presenter = BatteryStatusPresenter(
			view, coroutineHelper, soundPool, context, appHelper, intentFactory)
	}
b

bsimmons

03/03/2021, 1:21 PM
@Joost Klitsie Interesting. In this case, how would you actually run your
@Test
though? If you don't use
runBlocking {}
won't the test just immediately finish and not wait for the coroutine to finish first?
j

Joost Klitsie

03/03/2021, 1:30 PM
yeah I am thinking here, I have no native target so I on my JVM target I stlil have to wrap calls to suspend functions. But after that I don't have to wait, because all the coroutines that I launch or contects that I switch will be unconfined, meaning they are running directly on the current thread.
but from
@Test fun myTest() {  someClass.callToSuspendFunction() }
doesn't work of course, as you cannot directly call a suspend function
b

bsimmons

03/03/2021, 1:34 PM
So how would you write that on the JVM then? By using
launch
or something in your suspend fun?
j

Joost Klitsie

03/03/2021, 1:34 PM
@Test fun myTest() { runBlocking { someClass.callToSuspendFunction() } }
with runblocking you can call the suspend fun
b

bsimmons

03/03/2021, 1:37 PM
And using your setup will make sure that this is all run always on
Dispatchers.Unconfined
?
j

Joost Klitsie

03/03/2021, 1:38 PM
Yes, if you use a thing like coroutine helper to switch out all your dispatchers with an unconfined dispatcher during tests it should work
b

bsimmons

03/03/2021, 1:41 PM
Cool. Interesting
apparently there is also this test coroutine dispatcher
10 Views