declareMock<> not working with mockk in android unit test - android

i am trying to mock a repository class using declareMock and mockk but it doesn't seem to be working as i am getting the data from the real repository.
Version
implementation "org.koin:koin-androidx-viewmodel:2.1.6"
testImplementation "org.koin:koin-test:2.1.6"
Code
My application class (only relevant parts):
class MyApplication : Application() {
companion object {
val appModule = module {
single<RepositoryUserLists> { RepositoryUserListsImpl() }
}
}
override fun onCreate() {
super.onCreate()
startKoin {
androidLogger()
androidContext(this#MyApplication)
modules(appModule)
}
}
}
My test class (only relevant parts)
class TestRepositoryUserLists : KoinTest {
#get:Rule
val koinTestRule = KoinTestRule.create {
modules(MyApplication.appModule)
}
#get:Rule
val mockProvider = MockProviderRule.create { clazz ->
mockkClass(clazz.java.kotlin)
}
#Rule
#JvmField
val instantExecutorRule = InstantTaskExecutorRule()
private val repo: RepositoryUserLists by inject()
#Before
fun before() {
declareMock<RepositoryUserLists> {
every { getAllLists() } returns MutableLiveData(listOf(MyList("test list")))
}
//PROBLEM IS HERE
//Expected: a list containing one item names "test list".
//Actual: empty list (like in real repository).
repo.getAllLists().value
}
}
github issue: https://github.com/InsertKoinIO/koin/issues/841

Just posted it here for those migrating to a newer version of the koin like 2.2.2,
and the dependency test was failed with an error Missing MockProvider. Please use MockProvider.register() to register a new mock provider.
import io.mockk.every
import io.mockk.mockkClass
...
val app = koinApplication {
printLogger(Level.DEBUG)
modules(...)
}
MockProvider.register { mockkClass(it) }
with(app.koin) {
declareMock<MyService> {
every { someAction() } returns "some answer"
...
}
}
app.checkModules()

I think you need to use the given/will syntax inside the declareMock block
declareMock<RepositoryUserLists> {
given(getAllLists()).willReturn(MutableLiveData(listOf(MyList("test list"))))
}

Related

Android Test Koin NoBeanDefFoundException

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 deprecated ActivityTestRule(SplashScreenActivity::class.java, true, false)
I change val scenario = activityRule.scenario by val scenario = activityRule.launchActivity(null)
I use loadKoinModules and not startKoin in setUp
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) ?
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 !

Koin Unit testing how to verify whether a class method was called when injected by "by inject()"

I have a class for which I have written unit tests. The class injects 2 other classes via the constructor. However due to cyclic dependency issues, I had to inject one of the other dependencies via by inject().
My class looks as the follows:
class AuthUseCase(
private val accessTokenUseCase: AccessTokenUseCase,
private val refreshTokenRepo: RefreshTokenRepo
) : KoinComponent {
val notificationService: NotificationService by inject()
fun getSyncedAccessToken(loginResult: LoginResult): Token? {
return when (loginResult) {
is LoginResult.Success -> {
accessTokenUseCase.storeRefreshToken(loginResult.accessToken)
notificationService.init()
loginResult.accessToken.accessToken
}
is LoginResult.Failure -> {
null
}
}
}
}
I want to test if notificationService.init was fired or not in my test.
Normally it would be as simple as
verify(notificationService).init()
However I cannot understand how to mock this class. Any help would be highly appreciated.
Thanks to #Mariuz and through this post:
https://github.com/InsertKoinIO/koin/issues/197#issuecomment-429768448
The trick is to start an empty Koin container. And load mocked module in the test.
#Test
fun testGetValidAccessTokenIfInvalidAccessTokenIsPassed() {
notificationService = mock()
startKoin { }
loadKoinModules(module {
single {
notificationService
}
})
verify(notificationService).init()
}

How fix No definition found for class:'android.content.Context'. Check your definitions! in KoinTest

In my KoinTest extends
#Before
fun setUp() {
startKoin { modules(KoinStarter.getModules()) }
}
#Test
fun `should inject my components`() {
val settingsStore: SettingsStore = get()
assertNotNull(settingsStore)
}
I get error No definition found for class:'android.content.Context'. Check your definitions!
But my module located in KoinStarter.getModules()
val localDataModule = module {
factory<Resources> { get<Context>().resources }
single<SettingsStore> { SettingsStore(get<Context>()) }
}
You need to provide the application context in the androidContext() method.
Since your question is about testing specifically, in my case I had to pass application context via ApplicationProvider:
startKoin {
androidContext(ApplicationProvider.getApplicationContext())
.....
}
Don't forget to add the androidx dependency:
testImplementation 'androidx.test:core:1.0.0'

Android Koin simple object inject fail

I'm trying to learn Koin for dependency injection in android. I started to follow the example and try to inject very simple object by but I'm getting the error as NoBeanDefFoundException: No definition found for ...
here's my code
Gradle
// Koin
def koin_version = '2.0.1'
implementation "org.koin:koin-androidx-scope:$koin_version"
implementation "org.koin:koin-androidx-viewmodel:$koin_version"
implementation "org.koin:koin-androidx-ext:$koin_version"
Application onCreate()
override fun onCreate() {
super.onCreate()
startKoin{
androidLogger()
androidContext(this#Application)
listOf(applicationModule)
}
}
Modules.kt
val applicationModule = module {
factory { UserSession("email","password") }
}
but when I try to inject it in anywhere (Application, Activity, Fragment) as private val userSession: UserSession by inject() I get above mentioned error. Am I missing something?
You probably got confused by the syntax, you're supposed to call the method modules and provide it with the modules you want started.
The listOf return value is ignored in your case, you're supposed to do something like this:
startKoin {
androidLogger()
androidContext(this#Application)
modules(applicationModule)
}
Reference
try this.
KoinApp.kt
class KoinApp : MultiDexApplication() {
override fun onCreate() {
super.onCreate()
startKoin(this, listOf(appModule))
}
}
appModule.kt
#JvmField
val appModule = module {
single { DataRepository(get()) }
}
The answer will work, but for future proofing, I would still have the list of.
startKoin {
androidLogger()
androidContext(this#Application)
modules(listOf(applicationModule))
}
Modules.kt:
val appModule = module {
single { MyRepositoryImpl() }
}
val viewModelModule = module {
viewModel { MainViewModel(get()) }
}
MyApp.kt:
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this#MyApp)
modules(listOf(appModule, viewModelModule))
}
}
}
Go to AndroidManifest and add:
<application
android:name=".MyApp"
...
</application>
MainActivity.kt:
//ViewModel
private val vm:MainViewModel = get()

How to inject a mocked ViewModel in a RoboElectric test using Koin

I'm pretty new in Android development and currently, I'm testing a basic activity with Roboelectric and Koin.
Code:
class SplashActivity : AppCompatActivity() {
private val viewModel: LoginViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_splash)
Stetho.initializeWithDefaults(this)
val user = viewModel.getPersistedUser()
if (user != null) {
viewModel.setUser(user)
startActivity(HomeActivity.getStartIntent(this))
} else {
startActivity(LoginActivity.getStartIntent(this))
}
}
}
val appModule = module(override = true) {
...
viewModel<LoginViewModel>()
}
Now all I want to do in the test is to inject a mocked version of the viewModel to simulate the response of the method getPersistedUser.
How can I do that with Roboelectric and Koin?
First, if you want to write UI test for SplashActivity. Better to use Expresso testing framework (https://developer.android.com/training/testing/espresso)
Second, if you want to mock your viewmodel with Koin in your test, you can load your Koin modules then declare your viewmodel mock, code will be similar like this
class SplashActivityTest : AutoCloseKoinTest() {
private val viewModel: LoginViewModel by inject()
#Before
fun before() {
koinApplication {
loadModules(appModule)
}
declareMock<LoginViewModel> {
given(getPersistedUser()).willReturn { User(username, password) }
}
}
#Test
fun loadCurrentUserWhenActivityInit() {
// verify your test here
}
}
More details here https://start.insert-koin.io/#/getting-started/testing

Categories

Resources