提问者:小点点

Android测试Koin NobeandeFoundException


我正在尝试用Koin做一些Android测试,到目前为止,还不成功。

我想用Koin注入的ViewModel测试一个基本的activity。

我已经读了一些帖子,比如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)
    }
}

它在我的appbuild.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
}

和viewmodel

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.LaunchActivity(null)
  3. 更改VAL scenario=ActivityRule.scenario
  4. 我在setup
  5. 中使用loadkoinmodules而不是startkoin

会发生两件事

  1. 当我的测试单独启动时(通过Android Studio):它通过了。
  2. 当我的测试与其他测试一起启动时(通过类或使用connectedAndroidTest),只有一个测试通过,其他测试都是KO.

所以我有两个问题。

  1. 如何使此测试与ActivityScenariorule一起工作?
  2. 我如何才能使它们“全部”工作(而不是一个一个地启动它们以使它们工作)?

共1个答案

匿名用户

好吧,别问我它是怎么运作的,但我知道了。

首先,由于需要配置,我遵循了以下内容: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没有启动”……在安装程序中,我刚刚用startkoin块替换了loadkoinmodules

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

最后,它对一个测试有效,但其他测试都失败了,因为没有调用“KoInAppalreadyStartedException”,比如StopKoin()。所以我发现我应该扩展autoclosekointest而不是kointest。但没有成功。最后,我在startkoin之前加上了一个stopkoin(),现在,一切都像魅力一样工作了。

下面是我的完整代码

@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,我还将此代码添加到了我的两个应用程序

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

只是为了确定!