Hi i need to unit test some code that employs `Glo...
# coroutines
t
Hi i need to unit test some code that employs
GlobalScope
is there a Best Practice approach?
s
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
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
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
fair enough
Copy code
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
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
Copy code
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
show me your code
t
Copy code
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)
}
Copy code
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
okay and whats the issue? Id just wrap the thing as I shown in the snippet to make it testable
Copy code
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
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
Copy code
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
yea dont do that, run everything in App.onCreate, there context will be 100% available
🙌 1
277 Views