如何在Koin的经过测试的测试中注入使用androidContext的类?

时间:2019-02-07 19:31:09

标签: android junit kotlin dependency-injection koin

我的一个类具有Context类型的依赖项。在将Koin添加到我的项目之前,我通过对Application类的硬依赖性来初始化它:

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

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

现在,我想使用Koin注入这种依赖性。这是我定义模块的方式:

object AppModule {

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

我正在使用我的Application类(用Java编写)的onCreate方法来启动Koin:

startKoin(singletonList(AppModule.appModule));

我想用仪器化测试而不是单元测试来测试此类,因为我想使用真实上下文而不是模拟。这是我的测试:

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

    private val _profileRepository by inject<IProfileRepository>()

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

测试失败,并出现以下异常:

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

如果我像这样模拟上下文,我可以使其与单元测试一起使用:

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)
    }

如何使它作为具有真实上下文的仪器化测试工作?

5 个答案:

答案 0 :(得分:1)

显然,无法从Java类启动Koin并注入应用程序上下文。这意味着如果您的某个类需要从容器中获取上下文,则必须使用org.koin.android.ext.android.startKoin而不是org.koin.java.standalone.KoinJavaStarter.startKoin

由于我的Application类仍然是用Java编写的,因此我使用一种方法创建了一个名为KoinHelper的对象:

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

然后我从我的Application类的onCreate方法中调用了此方法:

KoinHelper.start(this);

现在,我在原始答案中发布的仪器化测试运行正常。

有关更多信息,请参见this issue on GitHub

答案 1 :(得分:0)

代替(在申请中):

startKoin(applicationContext, modules)

使用模拟的上下文:

startKoin(modules) with (mock(Context::class.java))

来自https://insert-koin.io/docs/1.0/documentation/koin-android/index.html#_starting_koin_with_android_context_from_elsewhere

答案 2 :(得分:0)

请检查文档中的this部分。它说

  

如果您需要从另一个Android类启动Koin,则可以使用   startKoin()函数并为您的Android Context实例提供   就像:

     

startKoin(androidContext,myAppModules)

因此,在仪器测试中,您可以在启动Koin时传递上下文。

@Before
fun before() {
        startKoin(InstrumentationRegistry.getContext(), listOf(AppModule.appModule))
    }

或者如果您需要应用程序级上下文

@Before
fun before() {
        startKoin(InstrumentationRegistry.getTargetContext(), listOf(AppModule.appModule))
    }

所引用的文档适用于1.0.1版

答案 3 :(得分:0)

关于在已测试的测试中获取Application上下文,您可以使用androidx.test.core.app.ApplicationProviderInstrumentationRegistry.targetContext.applicationContext

  @Before
  fun setUp() {
    stopKoin()
    loadKoinModules(testModule) with ApplicationProvider.getApplicationContext<Application>()
  }

...其中testModule使用androidApplication()来检索Application上下文:

val testModule = module {
  single {
    ToDoDatabase.newInstance(
      androidApplication(),
      memoryOnly = true
    )
  }
  single { ToDoRepository(get()) }
}

请注意,我的stopKoin()通话之所以存在,是因为我在覆盖自定义startKoin()子类中Application创建的现有模块时遇到困难。 ¯\_(ツ)_/¯

答案 4 :(得分:0)

@Before
fun setUp() {
    stopKoin()
    startKoin {
        androidContext(app) // for example ApplicationProvider.getApplicationContext<TestApplication>()
        modules(module1, module2)
    }
}