Koin: Getting Android's context for unit testing

Created on 31 May 2018  路  11Comments  路  Source: InsertKoinIO/koin

Hello,

Is there a way to get either the Android's context or resources to use for a unit test? The use case is, having a json file we can reference vs hard coding the json string into the class we are unit testing.

android accepted

Most helpful comment

Hello @arnaudgiuliani, Below is a sample unit test using what you suggested and having success on accessing the resources! Please let me know if there is anything else I can do to help.

//Make sure you add this in your gradle inside android {}
testOptions {
        unitTests.includeAndroidResources true
    }
@RunWith(RobolectricTestRunner::class)
class ContextTest : KoinTest {

    class ContextService(private val context: Context) {
        fun getString(stringID: Int): String = context.getString(stringID)
    }

    val contextService: ContextService by inject()

    @Before
    fun setUp() {
        val contextModule = applicationContext {
            bean { RuntimeEnvironment.application } bind Context::class
            factory { ContextService(get()) }
        }
        StandAloneContext.startKoin(listOf(contextModule))
    }

    @After
    fun tearDown() {
        StandAloneContext.closeKoin()
    }

    @Test
    fun `testing if we have access to context`() {
        Assert.assertEquals("context text", contextService.getString(R.string.context_text))
    }
}

All 11 comments

Hello,

with Junit and Mockito we can't recreate a "real" Android context. You can use more easily make such thing in Android instrumented tests.

I can be interesting to investigate with new nitrogen project, or more about Robolectric project if we can do something.

If I were to use Robolectric, I know they offer a way to grab the context, how can I pass it to one of my modules?

If you want to add/override the existing definition of Application, you just have to provide a module like:

val androidContextModule = applicationContext{
    bean { /* your instance of Application */ } bind Context::class
}

And then use the androidContextModule in the list of modules that you use, at the end of the list of modules: startKoin(listOf(module1, ..., androidContextModule))

Do we have a direct function to create an Android context with Robotlectric?

Hello @arnaudgiuliani, Below is a sample unit test using what you suggested and having success on accessing the resources! Please let me know if there is anything else I can do to help.

//Make sure you add this in your gradle inside android {}
testOptions {
        unitTests.includeAndroidResources true
    }
@RunWith(RobolectricTestRunner::class)
class ContextTest : KoinTest {

    class ContextService(private val context: Context) {
        fun getString(stringID: Int): String = context.getString(stringID)
    }

    val contextService: ContextService by inject()

    @Before
    fun setUp() {
        val contextModule = applicationContext {
            bean { RuntimeEnvironment.application } bind Context::class
            factory { ContextService(get()) }
        }
        StandAloneContext.startKoin(listOf(contextModule))
    }

    @After
    fun tearDown() {
        StandAloneContext.closeKoin()
    }

    @Test
    fun `testing if we have access to context`() {
        Assert.assertEquals("context text", contextService.getString(R.string.context_text))
    }
}

Great 馃憤
A sample/geetting started project will gather test samples for Android. I keep you in touch!

Unfortunately, I still have a problem with configuring robolectric and koin. I'm getting the following error

org.koin.error.AlreadyStartedException: Koin is already started. Run startKoin only once or use loadKoinModules

    at org.koin.standalone.StandAloneContext.startKoin(StandAloneContext.kt:124)
    at org.koin.standalone.StandAloneContext.startKoin$default(StandAloneContext.kt:121)
    at org.koin.sampleapp.ContextTest.setUp(ContextTest.kt:33)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
    at org.robolectric.internal.SandboxTestRunner$2.evaluate(SandboxTestRunner.java:253)
    at org.robolectric.internal.SandboxTestRunner.runChild(SandboxTestRunner.java:130)
    at org.robolectric.internal.SandboxTestRunner.runChild(SandboxTestRunner.java:42)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.robolectric.internal.SandboxTestRunner$1.evaluate(SandboxTestRunner.java:84)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMainV2.main(AppMainV2.java:131)

I've also copy and paste @jmiecz code and tried on @arnaudgiuliani koin's sample app with the same results.

In the end it works. I use version 1.0.0-beta-6

My code:

@RunWith(RobolectricTestRunner::class)
class MyViewModelTest : AutoCloseKoinTest() {

    val context: Context by inject()
    val repository = mock<CountryRepository>()
    val vm = MyViewModel(context, repository)

    @Test
    fun testA() {
        ...
    }

    @Test
    fun testB() {
        ...
    }

I don't have any @Before and @After nor specific modules for tests. Is my configuration alright or it works by accident? :)

Nop. For an instrumented test, your Koin container is already started via your startKoin().

What do you mean by instrumented tests @arnaudgiuliani ?
My tests are under test folder and run on jvm. Are they still called instrumented because of Robolectric

Yes kindof. This is unit test, with Android instrumented classes.

If you want to add/override the existing definition of Application, you just have to provide a module like:

val androidContextModule = applicationContext{
    bean { /* your instance of Application */ } bind Context::class
}

And then use the androidContextModule in the list of modules that you use, at the end of the list of modules: startKoin(listOf(module1, ..., androidContextModule))

Do we have a direct function to create an Android context with Robotlectric?

where to put it?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Jeevuz picture Jeevuz  路  4Comments

sankarsana picture sankarsana  路  4Comments

TedHoryczun picture TedHoryczun  路  4Comments

fmobus picture fmobus  路  4Comments

miladsalimiiii picture miladsalimiiii  路  3Comments