Issue
I'm trying to do some Android Tests with Koin and so far, it is not a success.
I want to test a basic Activity with a ViewModel, injected by Koin.
I already read posts like NoBeanDefFoundException with Mock ViewModel, testing with Koin, Espresso but so far I still have the error.
Here is the code relative to the tests configuration
A specific app that start with no module.
class MyTestApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin { emptyList<Module>() }
}
}
A specific runner that uses the test app
class OccazioTestRunner : AndroidJUnitRunner() {
override fun newApplication(
cl: ClassLoader?,
className: String?,
context: Context?
): Application {
return super.newApplication(cl, MyTestApplication::class.java.name, context)
}
}
That is defined in my app build.gradle
to be used as runner
android {
defaultConfig {
testInstrumentationRunner "fr.dsquad.occazio.occazio.OccazioTestRunner"
}
}
And now the code I want to test
In my MyActivity
class MyActivity : AppCompatActivity(R.layout.activity_my) {
private val myViewModel by viewModel<MyViewModel>()
// Some code
}
And the viewmodel
class MyViewModel(private val useCase: MyUseCase): ViewModel() {
// Some code
}
And finally, the test itself (in 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
}
}
And so far, everytime I run this code I have this error
org.koin.core.error.NoBeanDefFoundException:
No definition found for 'mypackage.MyViewModel' has been found. Check your module definitions.
What is interesting is that, when
- I change the rule
activityScenarioRule
by the old deprecatedActivityTestRule(SplashScreenActivity::class.java, true, false)
- I change
val scenario = activityRule.scenario
byval scenario = activityRule.launchActivity(null)
- I use
loadKoinModules
and notstartKoin
insetUp
Two things happen
- When my test is started alone (via Android Studio): it passes.
- When my test is started with other tests (by the class or with connectedAndroidTest), only one of them passes and old the others are KO.
So I have two questions in fact here.
- How can I make this test work with
activityScenarioRule
? - How can I make them "all" work (and not start them one by one to make them work) ?
Solution
Ok, don't ask me how it works but I figured it out.
First of all, as I needed config I followed this https://medium.com/stepstone-tech/better-tests-with-androidxs-activityscenario-in-kotlin-part-1-6a6376b713ea .
I've done 3 things
First, I needed to configure koin before startup, to do that, I needed to use ActivityScenario.launch()
with an intent that I defined earlier
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)
Then "KoinApp was not started"... I just replaced the loadKoinModules
block with the startKoin
one in setUp
startKoin { modules(module { viewModel { MyViewModel(mockUseCase) } }) }
Finally, it worked for 1 test, but the others were failing because "KoinAppAlreadyStartedException" like the stopKoin()
was not called. So I found out that I should extend AutoCloseKoinTest
instead of KoinTest
.. But no success.
In the end, I've put a stopKoin()
before the startKoin
and now, everything works like a charm.
Here is my complete code that works
@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, I've also added this code to my two Applications
override fun onTerminate() {
super.onTerminate()
stopKoin()
}
Just to be sure !
Answered By - Quentin Klein
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.