Open emanuelecastelli opened 3 days ago
Have you call startKoin
in your testing code? You can check out this docs for testing with koin: https://insert-koin.io/docs/reference/koin-test/testing/
Have you call
startKoin
in your testing code? You can check out this docs for testing with koin: https://insert-koin.io/docs/reference/koin-test/testing/
Yeah, it's started correctly, in fact the error i get is
org.koin.core.error.NoBeanDefFoundException: No definition found for type 'viewmodels.LoginViewModel' and qualifier 'loginVM'. Check your Modules configuration and add missing type and/or qualifier!
at org.koin.core.scope.Scope.throwDefinitionNotFound(Scope.kt:301)
at org.koin.core.scope.Scope.resolveValue(Scope.kt:271)
at org.koin.core.scope.Scope.resolveInstance(Scope.kt:233)
at org.koin.core.scope.Scope.get(Scope.kt:212)
at moe.tlaster.precompose.koin.KoinKt$resolveViewModel$1.invoke(Koin.kt:44)
at moe.tlaster.precompose.koin.KoinKt$resolveViewModel$1.invoke(Koin.kt:43)
at moe.tlaster.precompose.stateholder.StateHolder.getOrPut(StateHolder.kt:18)
....
You might need to try koinViewModel(ILoginViewModel::class, named("loginVM"))
instead of koinViewModel(LoginViewModel::class, named("loginVM"))
ILoginViewModel
it's an interface and i can not inherit it from ViewModel
as needed by koinViewModel
fun <T : ViewModel> koinViewModel(
vmClass: KClass<T>,
//...
interface ILoginViewModel : IBaseViewModel {
suspend fun getAppStartupCount(): Int?
suspend fun addAppStartupCount()
suspend fun resetAppStartupCount()
//...
Maybe i need some refactor on my side? Any suggestion is appreciated :)
Oops, my fault, I forgot the generic limitation of the koinViewModel
.
There're some workaround I can think of:
factory<LoginViewModel>
instead of factory<ILoginViewModel>
in your testModule
definition.IBaseViewModel
or ILoginViewModel
a abstract class and extend from ViewModel
.get<LoginViewModel>
in your testing code.Oops, my fault, I forgot the generic limitation of the
koinViewModel
. There're some workaround I can think of:
- Use
factory<LoginViewModel>
instead offactory<ILoginViewModel>
in yourtestModule
definition.- Make your
IBaseViewModel
orILoginViewModel
a abstract class and extend fromViewModel
.- If you're totally not care about Lifecycle things, you can just
get<LoginViewModel>
in your testing code.
Thx for reply. I need interface because MocKMP can mock just interfaces, so i can not use abstract classes. The problem is in composable function rather that in test: how can i retrieve from Koin the right VM instance and having it bound to lifecycle?
@Composable
fun LoginScreen() {
// val viewModel: ILoginViewModel = getKoinInstance() -> not bound to lifecycle
// val viewModel:ILoginViewModel = viewModel(LoginViewModel::class) {
// LoginViewModel()
// } -> same as following solution
val viewModel: ILoginViewModel = koinViewModel(LoginViewModel::class, named("loginVM")) // not retrieved from Koin DI cause i'm using interface
Im looking for an alternative lib for mocking, maybe with more luck :)
I starting to wonder how Koin it self will handle this use case in Android without PreCompose 🤔.
There's still a workaround, if you look into this file, which is some copy&&pasting and little editing from koin, you can make your own koinViewModel
function without the ViewModel
generic limitation.
I starting to wonder how Koin it self will handle this use case in Android without PreCompose 🤔. There's still a workaround, if you look into this file, which is some copy&&pasting and little editing from koin, you can make your own
koinViewModel
function without theViewModel
generic limitation.
Yeah this is the first idea that i had, but before start a pr i was looking for a pre baked solution 😀 I'll try and keep you informed 😉
ok @Tlaster i resolved it using this fun:
@Composable
fun <T : IBaseViewModel> resolveViewModel(
vmClass: KClass<T>,
stateHolder: StateHolder = checkNotNull(LocalStateHolder.current) {
"No StateHolder was provided via LocalStateHolder"
},
key: String? = null,
scope: Scope = LocalKoinScope.current,
qualifier: Qualifier? = null,
parameters: ParametersDefinition? = null,
): T {
return stateHolder.getOrPut(qualifier?.value ?: key ?: vmClass.canonicalName ?: "") {
scope.get(vmClass, qualifier, parameters)
}
}
I don't like very much the bounding to IBaseViewModel because maybe i will need to retrieve VM of other kind, but by now i can run tests and app with correctly injected/retrieved VM. Maybe you consider to include a fun like that in future?
In any case, thanks for the answers and for your work!
Hi, for testing purpose i need to inject/retrieve viewmodels inside composable functions using interfaces rather that concrete classes (Koin for DI). For mocking i use MocKMP, actually the only mocking lib in KMM env. It works well, but can mock just interfaces (with Mockito you can mock classes too).
Now i have something like this, but when testing koin can not find vm class injected. There isn't a way to find vm concrete class by it's interface in stateholder?
Thx