Testing android fragments with activityViewModels

Viewed 471

I'm trying to run android instrumentation tests for the fragments that share the same ViewModel. The ViewModel have some arguments. I use koin, but when using activityViewModels in the tests koin does not inject the ViewModel.

class SomeFragment() : Fragment() {
    private val viewModel: SomeViewModel by activityViewModels()
    ... more code
}

class SomeFragmentTest() : KoinTest{

    @Before
    fun setup() {
        val viewModel: SomeViewModel = mockk(relaxed = true)

        startKoin { loadKoinModules(listOf(
            module(override = true) { viewModel },
            module(override = true) { factory { appAnalytics } })) }
    }
.... more code
}

The message received is

Caused by: java.lang.InstantiationException: java.lang.Class<SomeViewModel> has no zero argument constructor
1 Answers

I found a solution, and I posted this question and I hope it helps others.

I added a view model factory this to the fragment:

class SomeFragment(val factory: (() -> ViewModelProvider.Factory)? = null) : Fragment() {
    private val viewModel: SomeViewModel by activityViewModels(factory)
    ... more code
}

class SomeFragmentTest() : KoinTest{
    private val fragmentFactory : FragmentFactory = mockk() 
    
    @Before
    fun setup() {
        val viewModel: SomeViewModel = mockk(relaxed = true)
        ... some mock related to viewModel
        val viewModelFactory : ViewModelProvider.Factory = mockk()
        every { viewModelFactory.create(SomeViewModel::class.java) } answers { viewModel }
        every { fragmentFactory.instantiate(any(), any()) } answers { SomeFragment{
            viewModelFactory
        }}
    }
    @Test
    fun test_fragment() {
        launchFragmentInContainer<SomeFragment>(
            themeResId = R.style.AppTheme,
            factory = fragmentFactory
        )
        ... some asserts 
    }

.... more code
}

This works for prod because in the activityViewModels implementation when the factory is null the default is used:

inline fun <reified VM : ViewModel> Fragment.activityViewModels(
    noinline factoryProducer: (() -> Factory)? = null
) = createViewModelLazy(VM::class, { requireActivity().viewModelStore },
    factoryProducer ?: { requireActivity().defaultViewModelProviderFactory })
Related