I have a method in my SearchViewModel and I want to test this method by Mockito and JUnit4.(searchCity())
but this error is shown after running the test:
kotlin.UninitializedPropertyAccessException:
lateinit property mRepository has not been initialized
SearchViewModel class :
class SearchViewModel #Inject constructor() : BaseViewModel() {
#Inject
lateinit var mRepository: DataRepository
#Inject
lateinit var sharedPfs: SharedPrefs
private var disposable: Disposable? = null
val search = MutableLiveData<ResponseSearch>()
val searchOWM = MutableLiveData<ResponseOWMCity>()
val searchCityName = MutableLiveData<String>()
val serachClick = SingleLiveEvent<Boolean>()
val progressBar = SingleLiveEvent<Boolean>()
val searchOWMvisibility = SingleLiveEvent<Boolean>()
val cityOWMclick = SingleLiveEvent<ResponseOWMCity>()
override fun getSharedPrefs(): SharedPrefs? {
return sharedPfs
}
fun stop() {
disposable?.let { if (!it.isDisposed) it.dispose() }
}
fun fabSearchClick(){
serachClick.call()
}
fun searchCity() {
val cityName = searchCityName.value
if (!Strings.isEmptyOrWhitespace(cityName)) {
progressBar.postValue(true)
disposable = mRepository.doSearchProcess(cityName)
?.subscribe({
search.postValue(it)
progressBar.postValue(false)
}, {
showToast(it!!.message!!)
progressBar.postValue(false)
})
} else{
showToast("لطفا شهر دلخواه خود را وارد کنید.")
}
}
fun searchCityOWM() {
val cityName = searchCityName.value
disposable = mRepository.doSearchProcessOWM(cityName)
?.subscribe({
if (it != null){
searchOWM.postValue(it)
searchOWMvisibility.postValue(true)
} else{
searchOWMvisibility.postValue(false)
}
}, {
searchOWMvisibility.postValue(false)
})
}
fun clickCityOWM(city: ResponseOWMCity){
cityOWMclick.postValue(city)
}
}
DataRepository class :
class DataRepository #Inject
constructor(private val endPointAPI: EndPointAPI, private val localRoomDatabse: LocalRoomDatabse) {
fun getAllSavedResults(): LiveData<List<City?>>? {
return localRoomDatabse.roomDao().getAllResults()
}
fun doSearchProcess(city: String?): Observable<ResponseSearch>? {
return endPointAPI.searchCities(Config.BASE_URL2 + city)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.doOnError({ throwable -> Log.i("1397", "remote: " + throwable.message) })
}
}
SearchViewModelTest :
class SearchViewModelTest {
#get:Rule
val mockitoRule: MockitoRule = MockitoJUnit.rule()
#get:Rule
val taskExecutorRule = InstantTaskExecutorRule()
#Rule
#JvmField
var testSchedulerRule = RxImmediateSchedulerRule()
#Mock
lateinit var observer: Observer<ResponseSearch>
#Mock
lateinit var mRepository: DataRepository
lateinit var searchViewModel: SearchViewModel
#Before
#Throws(Exception::class)
fun setUp() {
MockitoAnnotations.initMocks(this)
searchViewModel = SearchViewModel()
}
#Test
fun doSearchResultSuccessWithData() {
// GIVEN
val res = RESULT()
res.name = "shiraz"
val list = ArrayList<RESULT>()
list.add(res)
val search = ResponseSearch(list)
val observable = Observable.just(search)
// WHEN
searchViewModel.searchCityName.value = "shiraz"
searchViewModel.search.observeForever(observer)
whenever(mRepository?.doSearchProcess("shiraz")).thenReturn(observable)
searchViewModel.searchCity()
// THEN
assertNotNull(searchViewModel.search.value)
assertThat(searchViewModel.search.value?.results?.size, CoreMatchers.`is`(1))
}
}
can anyone help me?
While using Dagger and field injection, you should actually inject them with component or factory when it comes to ViewModels. While testing you can inject that mock you created here
#Mock
lateinit var mRepository: DataRepository
using auto-generated code by Dagger
#Before
#Throws(Exception::class)
fun setUp() {
MockitoAnnotations.initMocks(this)
searchViewModel = SearchViewModel()
SearchViewModel_MembersInjector.injectMRepository(searchViewModel, mRepository)
}
Related
This is my code that sign in and move to MainActivity(MainFragment)
#RunWith(AndroidJUnit4::class)
#ExperimentalCoroutinesApi
#LargeTest
#HiltAndroidTest
class LoginActivityTest {
#get:Rule
val hiltRule = HiltAndroidRule(this)
#get:Rule
val permissionRule = GrantPermissionRule.grant(Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
private lateinit var scenario: ActivityScenario<LoginActivity>
#Before
fun init() {
hiltRule.inject()
scenario = ActivityScenario.launch(LoginActivity::class.java)
}
#After
fun close(){
scenario.close()
}
#Test
fun signIn_moveToMainFragment() {
val email = "abc#naver.com"
val pw = "abc"
onView(withId(R.id.acet_email)).perform(typeText(email))
onView(withId(R.id.acet_password)).perform(typeText(pw))
onView(withId(R.id.acbtn_login)).perform(click())
assertEquals(MainActivity::class.java, EspressoHelper.getCurrentActivity())
}
}
here's EspressoHelper that I used:
object EspressoHelper {
fun getCurrentActivity(): Activity? {
var currentActivity: Activity? = null
getInstrumentation().runOnMainSync { run { currentActivity = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(
Stage.RESUMED).elementAtOrNull(0) } }
return currentActivity
}
}
This is viewmodel class.
#HiltViewModel
class MainViewModel #Inject constructor(private val mainRepository: MainRepos) : ViewModel() {
fun get(query:String) = liveData(Dispatchers.IO) {
emit(Resource.loading(data = null))
try {
val repos=mainRepository.getData(query)
emit(Resource.success(data = repos))
} catch (exception: Exception) {
emit(Resource.error(data = null, message = exception.message ?: "Error Occurred!"))
}
}
}
ViewModel test class
#ExperimentalCoroutinesApi
class MainViewModelTest {
#get:Rule
val instantTaskRule = InstantTaskExecutorRule()
#get:Rule
val mainCoroutineRule = MainCoroutineRule()
private lateinit var repository: FakeRepository
private lateinit var mainViewModel: MainViewModel
#Before
fun setUp() {
repository = FakeRepository(null)
mainViewModel= MainViewModel(repository)
}
#Test
fun `given api success, when loadData() is called, should show repos`() = runBlocking {
val result=mainViewModel.getRepos("").getOrAwaitValue()
Assert.assertNotNull(result.data!=null)
}
#After
fun tearDown() {
}
}
So, I have this ViewModel class
#HiltViewModel
class HomeViewModel #Inject constructor(private val starWarsRepository: StarWarsRepository) : ViewModel() {
/**
* Two-Way binding variable
*/
val searchQuery: MutableLiveData<String> = MutableLiveData()
val characters = searchQuery.switchMap { query ->
if (query.isNotBlank() && query.isNotEmpty()) {
characterPager.liveData.cachedIn(viewModelScope)
} else {
MutableLiveData()
}
}
fun retrySearch() {
searchQuery.postValue(searchQuery.value)
}
private val characterPager by lazy {
val searchPagingConfig = PagingConfig(pageSize = 20, maxSize = 100, enablePlaceholders = false)
Pager(config = searchPagingConfig) {
CharacterSearchPagingSource(starWarsRepository, searchQuery.value ?: String())
}
}
}
and here it's counter TestingClass
#ExperimentalCoroutinesApi
#RunWith(JUnit4::class)
class HomeViewModelTest {
#get:Rule
val instantTaskExecutionRule = InstantTaskExecutorRule()
private lateinit var homeViewModel: HomeViewModel
private val starWarsAPI = mock(StarWarsAPI::class.java)
private val testDispatcher = UnconfinedTestDispatcher()
#Before
fun setup() {
Dispatchers.setMain(testDispatcher)
homeViewModel = HomeViewModel(StarWarsRepositoryImpl(starWarsAPI, testDispatcher))
}
#AfterTest
fun tearDown() {
Dispatchers.resetMain()
}
#Test
fun `live data should emit success with result when searching for a character`() = runTest {
val character = CharacterEntity("", "", "", "172", "", listOf(), listOf())
`when`(starWarsAPI.searchCharacters("a", null)).thenReturn(CharacterSearchResultEntity(listOf(character, character), null, null))
homeViewModel.searchQuery.value = "a"
val result = homeViewModel.characters.getOrAwaitValue()
assertEquals(PagingSource.LoadResult.Page(listOf(), null, null), result)
}
}
Here is the repository
class StarWarsRepositoryImpl(private val starWarsAPI: StarWarsAPI, private val coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO) : StarWarsRepository {
override suspend fun searchCharacters(query: String, page: Int?) = withContext(coroutineDispatcher) {
starWarsAPI.searchCharacters(query, page)
}
}
I'm using this extension function from a google sample.
fun <T> LiveData<T>.getOrAwaitValue(time: Long = 2, timeUnit: TimeUnit = TimeUnit.SECONDS, afterObserve: () -> Unit = {}): T {
var data: T? = null
val latch = CountDownLatch(1)
val observer = object : Observer<T> {
override fun onChanged(o: T?) {
data = o
latch.countDown()
this#getOrAwaitValue.removeObserver(this)
}
}
this.observeForever(observer)
try {
afterObserve.invoke()
if (!latch.await(time, timeUnit)) {
throw TimeoutException("LiveData value was never set.")
}
} finally {
this.removeObserver(observer)
}
#Suppress("UNCHECKED_CAST")
return data as T
}
The problem is I'm getting an exception from the getOrAwaitValue that LiveData value was never set.?
I following the new google guide and end with this to represent the state in viewmodel like tis
var uiState: AccountSettingUiState by
mutableStateOf(AccountSettingUiState.Initial)
private set
then I have this function
fun resetPasswordUseCase(context: Context) {
resetPasswordUseCase.execute(context)
.subscribeOn(rxSchedulers.io)
.doOnSubscribe {
uiState = AccountSettingUiState.Loading
}
.observeOn(rxSchedulers.ui)
.subscribe {
uiState = AccountSettingUiState.Result
}
}
I want to test this function by assert emitting loading then result but how
I can capture the values
I can test only final state
ViewModel
#HiltViewModel
class SearchViewModel #Inject constructor(
private val searchItemUseCase: SearchItemUseCase,
private val searchUIMapper: SearchUIMapper
) : ViewModel() {
var search by mutableStateOf<StateUI<SearchUI>>(StateUI.Init())
fun searchItem(query: String) {
viewModelScope.launch {
search = StateUI.Loading()
searchItemUseCase.execute(query)
.catch { error ->
search = StateUI.Error(error)
}
.map {
searchUIMapper.map(it)
}.collect {
search = if (it.results.isEmpty()) {
StateUI.Error(EmptySearchException())
} else {
StateUI.Success(it)
}
}
}
}
}
Test
#ExperimentalCoroutinesApi
#ExperimentalTime
class SearchViewModelTest {
private lateinit var searchViewModel: SearchViewModel
#get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
#get:Rule
var coroutineRule = TestCoroutineRule()
#MockK(relaxed = true)
private lateinit var searchItemUseCase: SearchItemUseCase
private val searchUIMapper by lazy {
SearchUIMapper(ItemUIMapper())
}
#Before
fun setup() {
MockKAnnotations.init(this)
searchViewModel = SearchViewModel(
searchItemUseCase,
searchUIMapper
)
}
#Test
fun success() = coroutineRule.runBlockingTest {
val search = getSearchEntity(itemEntityList)
//Given
coEvery { searchItemUseCase.execute("") } returns flow {
emit(search)
}
searchViewModel.searchItem("")
assertEquals(StateUI.Success(searchUIMapper.map(search)), searchViewModel.search)
}
#Test
fun emptySearch() = coroutineRule.runBlockingTest {
val search = getSearchEntity(emptyList())
//Given
coEvery { searchItemUseCase.execute("") } returns flow {
emit(search)
}
//When
searchViewModel.searchItem("")
//Verify
assertTrue((searchViewModel.search as StateUI.Error<SearchUI>).data is EmptySearchException)
}
#Test
fun error() = coroutineRule.runBlockingTest {
val error = NoConnectivityException()
//Given
coEvery { searchItemUseCase.execute("") } returns flow {
throw error
}
//When
searchViewModel.searchItem("")
assertEquals(StateUI.Error<SearchUI>(error), searchViewModel.search)
}
}
I want to unit test my viewmodel and repository but I don't know how I can achieve that. I have made a start wit the viewmodeltestclass but I don't know how I can go further and what the best approach is. Should I also test my endpoint class and mainactivity? can someone help me please?
This is my endpoint class:
interface VenuesEndpoint {
#GET("v2/venues/search")
suspend fun get(
#Query("near") city: String,
#Query("limit") limit: String = Constants.LIMIT,
#Query("radius") radius: String = Constants.RADIUS,
#Query("client_id") id: String = Constants.CLIENT_ID,
#Query("client_secret") secret: String = Constants.CLIENT_SECRET,
#Query("v") date: String
): VenuesMainResponse
}
My repository class:
private val _data: MutableLiveData<VenuesMainResponse?> = MutableLiveData(null)
val data: LiveData<VenuesMainResponse?> get() = _data
suspend fun fetch(city: String, date: String) {
val retrofit = ApiClient()
val api = retrofit.retro.create(VenuesEndpoint::class.java)
try {
val response = api.get(
city = city,
date = date
)
_data.value = response
} catch (e: Exception) {
Log.d(TAG, e.message.toString())
_data.value = null
}
}
}
My viewmodel class:
class VenueViewModel() : ViewModel() {
private val repository = VenuesRepository()
val data: LiveData<VenuesMainResponse?> = repository.data
fun getData(city: String, date: String) {
viewModelScope.launch {
repository.fetch(city, date)
}
}
}
My mainActivity class:
class MainActivity : AppCompatActivity() {
private val viewModel by viewModels<VenueViewModel>()
private lateinit var adapter: HomeAdapter
private var searchData: List<Venue>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val editText = findViewById<EditText>(R.id.main_search)
viewModel.getData(
city = Constants.CITY,
date = Constants.DATE
)
viewModel.data
.observe(this, Observer {
it.let {
initAdapter()
rv_home.visibility = View.VISIBLE
if (it != null) {
adapter.setData(it.response.venues.sortedBy { myObject -> myObject.name })
searchData = it.response.venues.sortedBy { myObject -> myObject.name }
} else {
Toast.makeText(this, Constants.ERROR_MESSAGE_API, Toast.LENGTH_SHORT).show()
}
}
})
}
My ViewModelTest class:
#RunWith(AndroidJUnit4::class)
class VenueViewModelTest : TestCase() {
private lateinit var viewModel: VenueViewModel
#Before
public override fun setUp() {
super.setUp()
viewModel = VenueViewModel()
}
#Test
fun testVenueViewModel()
{
}
}