Search code examples
androidjunitkotlindependency-injectionkoin

How do I inject a class that uses androidContext in an instrumented test with Koin?


One of my classes has a dependency of type Context. Before adding Koin to my project, I initialized this with a hard dependency on my Application class:

class ProfileRepository(
    private var _context: Context? = null,
    private var _profileRestService: IProfileRestService? = null
) : IProfileRepository {

    init {
        if (_context == null) {
            _context = MyApplication.getInstance().applicationContext
        }
    }

Now, I want to use Koin to inject this dependency. This is how I've defined the module:

object AppModule {

    @JvmField
    val appModule = module {
        single<IProfileRestService> { ProfileRestService() }
        single<IProfileRepository> { ProfileRepository(androidContext(), get()) }
    }
}

I'm starting Koin in the onCreate method of my Application class (which is written in Java):

startKoin(singletonList(AppModule.appModule));

I want to test this class with an instrumented test and not a unit test because I want to use the real context and not a mock. This is my test:

@RunWith(AndroidJUnit4::class)
class MyTest : KoinTest {

    private val _profileRepository by inject<IProfileRepository>()

    @Test
    fun testSomething() {
        assertNotNull(_profileRepository)
    }

The test is failing with an exception:

org.koin.error.BeanInstanceCreationException: Can't create definition for 'Single [name='IProfileRepository',class='com.my.app.data.profile.IProfileRepository']' due to error :
No compatible definition found. Check your module definition

I can get it to work with a unit test if I mock the context like so:

class MyTest : KoinTest {

    private val _profileRepository by inject<IProfileRepository>()

    @Before
    fun before() {
        startKoin(listOf(AppModule.appModule)) with mock(Context::class.java)
    }

    @After
    fun after() {
        stopKoin()
    }

    @Test
    fun testSomething() {
        assertNotNull(_profileRepository)
    }

How can I make it work as an instrumented test with a real context?


Solution

  • Apparently there's no way to start Koin from a Java class and inject the application context. What that means is if one of your classes needs to get the context from the container, you must use org.koin.android.ext.android.startKoin instead of org.koin.java.standalone.KoinJavaStarter.startKoin.

    Since my Application class is still written in Java, I created an object called KoinHelper with one method:

    @JvmStatic
    fun start(application: Application) {
        application.startKoin(application, listOf(AppModule.appModule))
    }
    

    Then I called this from the onCreate method of my Application class:

    KoinHelper.start(this);
    

    Now, the instrumented test I posted in my original answer runs just fine.

    Please see this issue on GitHub for more info.