https://kotlinlang.org logo
Title
t

Tower Guidev2

04/14/2022, 11:58 AM
Hi i need to unit test some code that employs
GlobalScope
is there a Best Practice approach?
s

stojan

04/14/2022, 12:24 PM
inject the scope OR extract the logic into
suspend fun
, test that... then the only remaining untested part becomes
GlobalScope.launch { myFun() }
which is trivial
👍 1
u

ursus

04/14/2022, 2:42 PM
inject the dispatcher that drives your scope and make it synchrouns with Unconfined Or depends what myfun is doing, if it emits stuff to a Flow then you can test it via turbine library and you can even keep the global scope
t

Tower Guidev2

04/14/2022, 3:27 PM
i used
GlobalScope.launch {}
so that I could place disk reads/writes (AndroidKeyStore calls) off the main thread and also so they did not impact app start up time im employing the google tink library (
com.google.crypto.tink
) for my in app encryption
u

ursus

04/14/2022, 3:29 PM
fair enough
class Whatever(dispatcher) {
    private val scope = CoroutineScope(SupervisorJob() + dispatcher)

    fun foo() {
       scope.launch { ... }
    }
}
in real code pass in the Dispatchers.Io in test code pass in Dispatchers.Unconfined
t

Tower Guidev2

04/14/2022, 3:34 PM
i originally had an init{} method in my Application class however that seems to cause a Race Condition as tink sometimes failes with a null pointer as shown by this stacktrace
java.lang.NullPointerException: 
  at com.google.crypto.tink.integration.android.SharedPrefKeysetReader.<init> (SharedPrefKeysetReader.java:60)
  at com.google.crypto.tink.integration.android.AndroidKeysetManager$Builder.withSharedPref (AndroidKeysetManager.kt:162)
in real code pass in the Dispatchers.Io
in test code pass in Dispatchers.Unconfined
this is not possible as the code is in my
onCreate()
function within my Application class
u

ursus

04/14/2022, 3:39 PM
show me your code
t

Tower Guidev2

04/14/2022, 3:47 PM
override fun onCreate() {
    super.onCreate()

    GlobalScope.launch(<http://Dispatchers.IO|Dispatchers.IO>) {
        manageTink()
    }

    if (BuildConfig.DEBUG) manageStrictMode()

    DynamicColors.applyToActivitiesIfAvailable(this@MyApplication)

    upgradeSecurityProvider()
    registerActivityLifecycleCallbacks(this@MyApplication)
}
private fun manageTink() {
    try {
        AeadConfig.register()
        authenticatedEncryption = generateNewKeysetHandle().getPrimitive(Aead::class.java)
    } catch (e: GeneralSecurityException) {
        throw RuntimeException(e)
    } catch (e: IOException) {
        throw RuntimeException(e)
    }
}
u

ursus

04/14/2022, 3:51 PM
okay and whats the issue? Id just wrap the thing as I shown in the snippet to make it testable
App.onCreate() {
    TinkInitializer(this, <http://Dispatcher.Io|Dispatcher.Io>)
}
``````
or have a explicit init function and dont do it in the ctor but it doesnt matter in your case, youre not doing anything there
t

Tower Guidev2

04/14/2022, 3:57 PM
the issue was that my code looked like this before i refactored it.... e.g. i called
manageTink()
from an
init
block in my Application class
init {
        GlobalScope.launch(<http://Dispatchers.IO|Dispatchers.IO>) {
            manageTink()
        }
    }
and as i pass
this@MyApplication
within the
manageTink()
function "sometimes" the
appContext
was found to be
null
u

ursus

04/14/2022, 4:19 PM
yea dont do that, run everything in App.onCreate, there context will be 100% available
🙌 1