google / dagger

A fast dependency injector for Android and Java.
https://dagger.dev
Apache License 2.0
17.41k stars 2.01k forks source link

@HiltViewModel Unit test problem. #4212

Closed Jaehwa-Noh closed 7 months ago

Jaehwa-Noh commented 7 months ago

Hello, I'm trying to test ViewModel on Unit test. But, I can't use @Inject on ViewModel. I can't found any documentation about @HiltViewModel Unit test. How can I test it? Is there any documentation?

I'm using compose.

My ViewModel

@HiltViewModel
class AmphibiansViewModel @Inject constructor(
    val amphibiansInfoRepository: AmphibiansInfoRepository
) : ViewModel() {

    private var _amphibiansUiState: MutableStateFlow<AmphibiansUiState> =
        MutableStateFlow(AmphibiansUiState.Loading)
    val amphibiansUiState: StateFlow<AmphibiansUiState> = _amphibiansUiState.asStateFlow()

    init {
        getAmphibiansList()
    }

    fun getAmphibiansList() {
        viewModelScope.launch {
            _amphibiansUiState.value = AmphibiansUiState.Loading
            _amphibiansUiState.value = try {
                AmphibiansUiState.Success(amphibians = amphibiansInfoRepository.getAmphibiansInfo())
            } catch (e: IOException) {
                AmphibiansUiState.Error(e.localizedMessage ?: "")
            } catch (e: HttpException) {
                AmphibiansUiState.Error(e.localizedMessage ?: "")
            }
        }
    }
}

sealed interface AmphibiansUiState {
    data class Success(val amphibians: List<AmphibiansInfoApiModel>) : AmphibiansUiState
    data class Error(val errorMessage: String) : AmphibiansUiState
    object Loading : AmphibiansUiState
}

And unit test code.

@HiltAndroidTest
class AmphibiansViewModelTest {
    @get:Rule(order = 0)
    val hiltRule = HiltAndroidRule(this)

    @get:Rule(order = 1)
    val testDispatcher = TestDispatcherRule()

    @Inject
    lateinit var amphibiansViewModel: AmphibiansViewModel

    @Before
    fun injectHiltRule() {
        hiltRule.inject()
    }

    @Test
    fun amphibiansViewModel_GetAmphibiansInfo_GetAmphibiansListSuccess() {
        assertEquals(
            AmphibiansUiState.Success(FakeAmphibiansDataSource.getAmphibiansInfo),
            amphibiansViewModel.amphibiansUiState.value
        )
    }
}

This is error message.

error: [dagger.hilt.android.processor.internal.viewmodel.ViewModelValidationPlugin] 
  public abstract static class SingletonC implements AmphibiansApplication_GeneratedInjector,
                         ^
  Injection of an @HiltViewModel class is prohibited since it does not create a ViewModel instance correctly.
  Access the ViewModel via the Android APIs (e.g. ViewModelProvider) instead.
  Injected ViewModel: com.example.myapplication.ui.AmphibiansViewModel

      com.example.myapplication.ui.AmphibiansViewModel is injected at
          com.example.myapplication.AmphibiansViewModelTest.amphibiansViewModel
      com.example.myapplication.AmphibiansViewModelTest is injected at
          com.example.myapplication.AmphibiansViewModelTest_GeneratedInjector.injectTest(com.example.myapplication.AmphibiansViewModelTest)
dmapr commented 7 months ago

Personally I've always written JVM tests for ViewModels where you just instantiate them normally and only wrote Android tests for components using the VMs — Fragments, Activities, compose functions. Any reason you don't want to write AmphibiansViewModelTest as a JVM test?

Jaehwa-Noh commented 7 months ago

Thank you. I misunderstand the documentation in Unit test and UI test section. So I change the code like this, and it works very well.

class AmphibiansViewModelTest {
    @get:Rule
    val testDispatcher = TestDispatcherRule()

    private lateinit var amphibiansViewModel: AmphibiansViewModel

    @Before
    fun injectHiltRule() {
        amphibiansViewModel = AmphibiansViewModel(amphibiansInfoRepository = FakeAmphibiansInfoRepository())
    }

    @Test
    fun amphibiansViewModel_GetAmphibiansInfo_GetAmphibiansListSuccess() {
        assertEquals(
            AmphibiansUiState.Success(FakeAmphibiansDataSource.getAmphibiansInfo),
            amphibiansViewModel.amphibiansUiState.value
        )
    }
}