Android测试Koin NoBeanDefFoundException

问题描述

我正在尝试使用Koin进行一些Android测试,到目前为止,这并不成功。

我想用由Koin注入的viewmodel测试基本活动。

我已经读过NoBeanDefFoundException with Mock ViewModel,testing with Koin,Espresso之类的帖子,但到目前为止,我仍然遇到错误


这是与测试配置相关的代码

没有任何模块的特定应用。

class MyTestApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin { emptyList<Module>() }
    }
}

使用测试应用的特定跑步者

class OccazioTestRunner : AndroidJUnitRunner() {
    override fun newApplication(
        cl: ClassLoader?,className: String?,context: Context?
    ): Application {
        return super.newApplication(cl,MyTestApplication::class.java.name,context)
    }
}

在我的应用build.gradle中定义为跑步者

android {
    defaultConfig {
       testInstrumentationRunner "fr.dsquad.occazio.occazio.OccazioTestRunner"
    }
}

现在我要测试的代码

在我的MyActivity

class MyActivity : AppCompatActivity(R.layout.activity_my) {

    private val myviewmodel by viewmodel<Myviewmodel>()

    // Some code
}

和视图模型

class Myviewmodel(private val useCase: MyUseCase): viewmodel() {
   // Some code
}


最后,测试本身(在androidTest中)

@LargeTest
class MyActivityTest : KoinTest {

    private lateinit var mockUseCase: MyUseCase

    @JvmField
    @Rule
    val activityRule = activityScenarioRule<MyActivity>()

    @Before
    fun setup() {
        mockUseCase = mock(MyUseCase::class.java)

        startKoin {
            modules(module { viewmodel { Myviewmodel(mockUseCase) } })
        }

        // I've also tried this
        loadKoinModules(
            module { viewmodel { Myviewmodel(mockUseCase) } }
        )
    }

    @After
    fun cleanUp() {
        stopKoin()
    }

    @Test
    fun sometest() = runBlocking {
        // Mock the usecase response
        `when`(mockUseCase.doSomething()).thenReturn("taratata")

        // Start the scenario
        val scenario = activityRule.scenario

        // Verify we call the getUserId
        // Activity is supposed to call the view model that will call the method doSomethingAdterThat.
        verify(mockUseCase,times(1)).doSomethingAfterThat()

        return@runBlocking
    }
}

到目前为止,每次我运行这段代码时,我都会遇到此错误

org.koin.core.error.NoBeanDefFoundException: 
No deFinition found for 'mypackage.Myviewmodel' has been found. Check your module deFinitions.

有趣的是,什么时候

  1. 我通过已弃用的旧activityScenarioRule更改了规则 ActivityTestRule(SplashScreenActivity::class.java,true,false)
  2. 我将val scenario = activityRule.scenario更改为val scenario = activityRule.launchActivity(null)
  3. 我在loadKoinModules中使用startKoin而不是setUp

发生两件事

  1. 我的测试(通过Android Studio)单独开始时:通过。
  2. 当我的测试开始于其他测试(通过类或connectedAndroidTest)时,其中只有一个通过了,而其他则通过了。

所以我实际上有两个问题。

  1. 如何使此测试适用于activityScenarioRule
  2. 我如何使它们“全部”工作(而不是一开始就使它们工作)?

解决方法

好吧,不要问我它是如何工作的,但我知道了。

首先,在需要配置时,我遵循了https://medium.com/stepstone-tech/better-tests-with-androidxs-activityscenario-in-kotlin-part-1-6a6376b713ea

我已经做了三件事

首先,我需要在启动之前配置koin,为此,我需要使用ActivityScenario.launch()并具有我先前定义的意图

private val intent = Intent(ApplicationProvider.getApplicationContext(),MyActivity::class.java)
var activityRule : ActivityScenario<MyActivity>? = null

// And then I can start my activity calling
activityRule = ActivityScenario.launch(intent)

然后“ KoinApp未启动” ...我只是在setUp中将loadKoinModules替换为startKoin

startKoin { modules(module { viewModel { MyViewModel(mockUseCase) } }) }

最后,它可以进行1次测试,但其他失败,因为未调用stopKoin()之类的“ KoinAppAlreadyStartedException”。 So I found out that I should extend AutoCloseKoinTest instead of KoinTest ..但是没有成功。 最后,我将stopKoin()放在startKoin之前,现在,所有内容都像魅力一样。

这是我可以使用的完整代码

@LargeTest
class MyActivityTest : KoinTest() {

    private val intent = Intent(ApplicationProvider.getApplicationContext(),MyActivity::class.java)
    var activityRule : ActivityScenario<MyActivity>? = null

    private lateinit var mockUseCase: MyUseCase

    @Before
    fun setup() {
        mockUseCase = mock(MyUseCase::class.java)
        stopKoin()
        startKoin {
            modules(module { viewModel { MyViewModel(mockUseCase) } })
        }
    }

    @After
    fun cleanUp() {
        activityRule?.close()
    }

    @Test
    fun someTest() = runBlocking {
        // Mock the usecase response
        `when`(mockUseCase.doSomething()).thenReturn("taratata")

        // Start the rule
        val activityRule = ActivityScenario.launch(intent)

        // Verify we call the getUserId
        // Activity is supposed to call the view model that will call the method doSomethingAdterThat.
        verify(mockUseCase,times(1)).doSomethingAfterThat()

        return@runBlocking
    }

}

Ho,我还将此代码添加到了我的两个Applications

override fun onTerminate() {
    super.onTerminate()
    stopKoin()
}

请确定!