I am new to JUnit test cases in Android. I heard that by using Mockito we can achieve easily.
My Android Class makes an external call to a REST API Service (Retrofit) that returns a JSON response. I have to mock that response (hardcoded JSON) and write test cases.
Please share your idea how to achieve this.
i am able to achieve.
updating with a piece of code.
class GenerateTripViewModelTest {
#Mock
private lateinit var mockRepository: GenerateTripRepository
private val schedulerProvider = SchedulerProvider(Schedulers.trampoline(), Schedulers.trampoline())
private lateinit var generateTripViewModel: GenerateTripViewModel
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
generateTripViewModel = GenerateTripViewModel(mockRepository, schedulerProvider)
}
#Test
fun SearchSuccessCase() {
val tripReq = GenerateTripReqModel(ArrayList<String>(),"123","xxxx","xxxx")
Mockito.`when`(mockRepository.generateTrip(tripReq)).thenReturn(Observable.just(GenerateTripResModel("")))
val testObserver = TestObserver<GenerateTripResModel>()
generateTripViewModel.generateTrip(tripReq).subscribe(testObserver)
testObserver.assertNoErrors()
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
class GenerateTripViewModel #Inject constructor(private val generateTripRepository: GenerateTripRepository,private val schedulerProvider: SchedulerProvider) : ViewModel() {
fun generateTrip(reqModel: GenerateTripReqModel) = generateTripRepository.generateTrip(reqModel)
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
#Singleton
class GenerateTripRepository #Inject constructor(
private val apiServices: ApiServices, private val context: Context,
private val appExecutors: AppExecutors = AppExecutors()) {
/**
* Generate Trip
*/
fun generateTrip(reqModel: GenerateTripReqModel): Observable<GenerateTripResModel> = apiServices.generateTrip(reqModel)
}
class Test{
#Mock
lateinit var redditApiService: RedditApiService
lateinit var postSettingsViewModel: PostSettingsViewModel
#Before
fun setUp() {
initMocks(this)
postSettingsViewModel = PostSettingsViewModel(redditApiService, userRepo)
}
#Test
fun testApi(){
Mockito.`when`(redditApiService.getSubreddits("asd")).thenReturn(Single
.just<SubredditResponse>(SubredditResponse(listOf(Subreddit("first")))))
//make your tests
}
}
You can use viewmodel or presenter and pass your api service there. With mockito you can specify function call and its return value. Mockito.when(api.get()).thenReturn(new Result()).
You can check google samples: https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/test/java/com/android/example/github/api/GithubServiceTest.kt
You can use product flavors for mock your response or user MockWebServer library or replace your retrofit interface with you implementation what return json from assets
upd: I misunderstood the question. I agree with Phowner Biring
Related
I am using Hilt for DI in my project. I am trying write unit test cases for LiveData object, but it's not coming under coverage.
ViewModel
#HiltViewModel
class HealthDiagnosticsViewModel #Inject constructor(
private var networkHelper: NetworkHelper
) : ViewModel() {
var result = MutableLiveData<Int>()
.....
}
My unit test class is as below:
HealthViewModelTest
#HiltAndroidTest
#RunWith(RobolectricTestRunner::class)
#Config(application = HiltTestApplication::class)
class HealthDiagnosticsViewModelTest{
#get:Rule
var hiltRule = HiltAndroidRule(this)
#Inject
lateinit var networkHelper: NetworkHelper
lateinit var healthDiagnosticsViewModel: HealthDiagnosticsViewModel
#Before
fun setUp() {
hiltRule.inject()
healthDiagnosticsViewModel = HealthDiagnosticsViewModel(networkHelper)
}
#Test
fun testGetResult() {
val result = healthDiagnosticsViewModel.result.value
Assert.assertEquals(null, result)
}
#Test
fun testSetResult() {
healthDiagnosticsViewModel.result.value = 1
Assert.assertEquals(1, healthDiagnosticsViewModel.result.value)
}
}
Test Cases are passed but it's not coming under method coverage.
I'll share with you the an example of my code that would solve your problem.
I'm usnig ViewModel with Dagger Hilt
You don't have to use Robelectric, you can use MockK library.
Replace your HiltRule with this Rule:
#get:Rule
var rule: TestRule = InstantTaskExecutorRule()
This is my ViewModel class
using MockK, you can mock the networkHelper class without Hilt.
So, your setup method will be like that:
lateinit var networkHelper: NetworkHelper
......
......
......
#Before
fun setUp() {
networkHelper = mockk<NetworkHelper>()
healthDiagnosticsViewModel = HealthDiagnosticsViewModel(networkHelper)
}
4)The most important part in your test is to Observe to the LiveData first.
#Test
fun testGetResult() {
healthDiagnosticsViewModel.result.observeForever {}
val result = healthDiagnosticsViewModel.result.value
Assert.assertEquals(null, result)
}
You can observe to the livedata for each unit test, but keep in mind to Observe first before change data.
I'm trying to test this repository using a unit test with JUnit 4
class AppRepository #Inject constructor(
private val networkHelper: NetworkHelper,
private val weatherMapper: WeatherMapper,
private val cacheHelper: CacheHelper
) : Repository {
override fun fetchWeatherInfo(lat: Double, lng: Double): Flow<AppResult<Weather>> {
return networkHelper.getWeatherInfo(lat, lng).map {
when(it){
is AppResult.Error -> AppResult.Error(it.errorMessage)
is AppResult.Success -> AppResult.Success(weatherMapper.map(it.data))
}
}
}
}
So I've made a unit test class like this using coroutine dispatcher and mock some interfaces
for repository
#ExperimentalCoroutinesApi
#RunWith(JUnit4::class)
class AppRepositoryTest{
private val dispatcher = TestCoroutineDispatcher()
#Mock
private lateinit var networkHelper: NetworkHelper
#Mock
private lateinit var cacheHelper: CacheHelper
private val mapper=WeatherMapper()
private lateinit var repository: AppRepository
#Before
fun setup(){
MockitoAnnotations.openMocks(this)
repository= AppRepository(networkHelper,mapper,cacheHelper)
}
#Test
fun`check if weather info returned failed`(){
dispatcher.runBlockingTest {
val expected:AppResult<Weather> = AppResult.Error("Can't load weather info")
`when`(networkHelper.getWeatherInfo(1.0,1.0)).thenReturn(
flow { AppResult.Error("Can't load weather info") }
)
val result= repository.fetchWeatherInfo(1.0,1.0).single()
assertEquals(expected,result)
}
}
}
I want to test a failed case for the current repository function but the failed crashed due to this error
Flow is empty
java.util.NoSuchElementException: Flow is empty
at kotlinx.coroutines.flow.FlowKt__ReduceKt.single(Reduce.kt:62)
at kotlinx.coroutines.flow.FlowKt.single(Unknown Source)
at com.isma3il.photoweatherapp.data.repositories.AppRepositoryTest$check if weather info returned failed$1.invokeSuspend(AppRepositoryTest.kt:73)
at com.isma3il.photoweatherapp.data.repositories.AppRepositoryTest$check if weather info returned failed$1.invoke(AppRepositoryTest.kt)
at com.isma3il.photoweatherapp.data.repositories.AppRepositoryTest$check if weather info returned failed$1.invoke(AppRepositoryTest.kt)
so how to fix this issue and how to use flow in testing?
I totally forget to emit data inside the flow builder
like this
`when`(networkHelper.getWeatherInfo(1.0,1.0)).thenReturn(
flow { emit(AppResult.Error("Can't load weather info")) }
)
and that is works for me.
I started to implement automated tests recently and I have a little doubt of how to test my ViewModel functions. My project follows some clean architecture concepts, I'm using lib kotlin for mockito (https://github.com/nhaarman/mockito-kotlin) and I would like to do the following test: When call makeLoginUser get success then set loginUserLiveData .
I have studied some concepts and I know the tests I should do, but I am still in doubt as to how to do them, which objects should I mock for a given test and which should be instantiated. I needed a small example to address me.
Here is my test class and some classes of the structure of my project.
TestClass
#RunWith(JUnit4::class)
class MainViewModelTest {
#get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
#Mock
private lateinit var myRepository: MyRepository
private lateinit var loginUserUseCase: LoginUserUseCase
private lateinit var checkUserAuthenticatedUsecase: CheckUserAuthenticatedUsecase
private lateinit var logoutUserUseCase: LogoutUserUseCase
private lateinit var mainViewModel: MainViewModel
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
}
#Test
fun `When call makeLoginUser get success then set loginUserLiveData`() {
// prepare
myRepository = mock { // should I mock or instantiate?
on { execLoginUser(ArgumentMatchers.anyString(), ArgumentMatchers.anyString()) } doReturn Flowable.just(true)
}
loginUserUseCase = mock { // should I mock or instantiate?
on { execute(ArgumentMatchers.anyMap()) } doReturn Flowable.just(true)
}
mainViewModel = MainViewModel(loginUserUseCase, checkUserAuthenticatedUsecase, logoutUserUseCase)
val observer = mock<Observer<ResultState<Boolean>>> { mock }
// Execute
mainViewModel.makeLoginUser("test#gmail.com", "123456")
// Check
// ?
}
}
ViewModel
// components 'LoginUserUseCase', 'CheckUserAuthenticatedUsecase' and 'LogoutUserUseCase' injected via koin
class MainViewModel(
private val loginUserUseCase: LoginUserUseCase,
private val checkUserAuthenticatedUsecase: CheckUserAuthenticatedUsecase,
private val logoutUserUsecase: LogoutUserUseCase
): BaseViewModel() {
val loginUserLiveData = MutableLiveData<ResultState<Boolean>>()
val userAuthenticatedLiveData = MutableLiveData<ResultState<Boolean>>()
val logoutUserLiveData = MutableLiveData<ResultState<Boolean>>()
fun makeLoginUser(email: String, password: String) {
loginUserLiveData.postValue(ResultState.Loading())
loginUserUseCase.execute(mapOf(EMAIL to email, PASSWORD to password))
.subscribe({
loginUserLiveData.postValue(ResultState.Success(it))
}, {
loginUserLiveData.postValue(ResultState.Error(it))
}).addTo(disposables)
}
...
}
UseCase Domain Class
// components 'ThreadExecutor' and 'PostExecutionThread' injected via koin
abstract class FlowableUseCase<T, in Params> constructor(
private val threadExecutor: ThreadExecutor,
private val postExecutionThread: PostExecutionThread) {
protected abstract fun buildUseCaseObservable(params: Params? = null): Flowable<T>
open fun execute(params: Params? = null): Flowable<T> {
return this.buildUseCaseObservable(params)
.subscribeOn(Schedulers.from(threadExecutor))
.observeOn(postExecutionThread.scheduler)
}
}
Abstraction UseCases
// components 'MyRepository', 'ThreadExecutor' and 'PostExecutionThread' injected via koin
// 'MyRepository' abstracts remote and local data repository
class LoginUserUseCase(
private val myRepository: MyRepository,
threadExecutor: ThreadExecutor,
postExecutionThread: PostExecutionThread
): FlowableUseCase<Boolean, Map<String, Any>?>(threadExecutor, postExecutionThread) {
override fun buildUseCaseObservable(params: Map<String, Any>?) = myRepository
.execLoginUser(
params?.get(EMAIL) as String,
params[PASSWORD] as String
)
}
i'm try to test my ViewModel with mockito.
This is my TestClass:
#RunWith(JUnit4::class)
class RatesViewModelTest {
#Rule #JvmField
open val instantExecutorRule = InstantTaskExecutorRule()
#Mock
var observer: Observer<Pair<ArrayList<CurrencyExchangerModel>,Boolean>>? = null
#Mock
private lateinit var repository: RatesRepository
private var currencyList = ArrayList<CurrencyModel>()
private lateinit var viewModel : RatesViewModel
#Before
fun setUp(){
MockitoAnnotations.initMocks(this)
currencyList.add(CurrencyModel("BASE"))
viewModel = RatesViewModel(repository!!)
viewModel.getCurrencyExchangerObservableList().observeForever(observer!!)
}
#Test
fun testNull(){
Mockito.`when`(repository.getFlowableRates()).thenReturn( Flowable.just(currencyList) )
assertTrue(viewModel.getCurrencyExchangerObservableList().hasObservers())
}
}
When this method is invoked:
Mockito.`when`(repository.getFlowableRates()).thenReturn( Flowable.just(currencyList) )
I got this error:
kotlin.UninitializedPropertyAccessException: lateinit property db has
not been initialized
Here the repository:
open class RatesRepository(context:Context) : BaseRepository(context){
#Inject
lateinit var ratesAPI: RatesAPI
#Inject
lateinit var db: Database
/**
* load the updated chatList from API
*/
fun loadCurrencyRatesFromAPI(): Single<ArrayList<CurrencyModel>> {
val supportedCurrency = context.resources.getStringArray(R.array.currencies)
return ratesAPI.getLatestRates(EUR_CURRENCY_ID).map { RatesConverter.getRatesListFromDTO(it,supportedCurrency) }
}
/**
* save rates on DB
*/
fun saveCurrencyRatesOnDB(list:ArrayList<CurrencyModel>): Completable {
return db.currencyRatesDAO().insertAll(list)
}
/**
* get flawable rates from DB
*/
fun getFlowableRates(): Flowable<List<CurrencyModel>> {
return db.currencyRatesDAO().getAll()
}
companion object {
const val EUR_CURRENCY_ID = "EUR" //BASE
}
}
What i'm doing wrong ?
Thx !
When you define behaviour of a mock and use the when(...).then(...) notation of mockito,
the method itself is invoked (by mockito, normally not relevant for your test).
In your case that is a problem because db is not initialized.
To avoid this issues use the doReturn(...).when(...) syntax in these cases,
which does not cause the method invocation.
Mockito.doReturn(Flowable.just(currencyList)).when(repository).getFlowableRates();
(You might need to adjust this java syntax to make it kotlin compatible)
I'm learning testing on Android with Mockito and Robolectric. I created very simple app in Kotlin with RxJava and Dagger2, using Clean Architecture. Everything works well on device, but I can't make my test pass. Here is my LoginPresenterTest:
#RunWith(RobolectricGradleTestRunner::class)
#Config(constants = BuildConfig::class)
public class LoginPresenterTest {
private lateinit var loginPresenter: LoginPresenter
#Rule #JvmField
public val mockitoRule: MockitoRule = MockitoJUnit.rule()
#Mock
private lateinit var mockContext: Context
#Mock
private lateinit var mockLoginUseCase: LoginUseCase
#Mock
private lateinit var mockLoginView: LoginView
#Mock
private lateinit var mockCredentialsUseCase: GetCredentials
#Before
public fun setUp() {
loginPresenter = LoginPresenter(mockCredentialsUseCase, mockLoginUseCase)
loginPresenter.view = mockLoginView
}
#Test
public fun testLoginPresenterResume(){
given(mockLoginView.context()).willReturn(mockContext)
loginPresenter.resume();
}
}
LoginPresenter contructor:
class LoginPresenter #Inject constructor(#Named("getCredentials") val getCredentials: UseCase,
#Named("loginUseCase") val loginUseCase: LoginUseCase) : Presenter<LoginView>
in loginPresenter.resume() i have:
override fun resume() {
getCredentials.execute(GetCredentialsSubscriber() as DefaultSubscriber<in Any>)
}
And, finally, GetCredentials:
open class GetCredentials #Inject constructor(var userRepository: UserRepository,
threadExecutor: Executor,
postExecutionThread: PostExecutionThread):
UseCase(threadExecutor, postExecutionThread) {
override fun buildUseCaseObservable(): Observable<Credentials> = userRepository.credentials()
}
The problem is, that every field in GetCredentials is null. I think I miss something (I took pattern from this project: https://github.com/android10/Android-CleanArchitecture), but I can't find what is it. Does anyone know what may cause this?
You're using a mock instance of GetCredentials (#Mock var mockCredentialsUseCase: GetCredentials) that's why you have nulls in its fields. It's rarely a good idea to mock everything apart from the main class under test (LoginPresenter). One way to think of this is to divide the dependencies into peers and internals. I would rewrite the test to something like:
inline fun <reified T:Any> mock() : T = Mockito.mock(T::class.java)
#RunWith(RobolectricGradleTestRunner::class)
#Config(constants = BuildConfig::class)
public class LoginPresenterTest {
val mockContext:Context = mock()
val mockLoginView:LoginView = mock().apply {
given(this.context()).willReturn(mockContext)
}
val userRepository:UserRepository = mock() // or some in memory implementation
val credentials = GetCredentials(userRepository, testThreadExecutor, testPostThreadExecutor) // yes, let's use real GetCredentials implementation
val loginUseCase = LoginUseCase() // and a real LoginUseCase if possible
val loginPresenter = LoginPresenter(credentials, loginUseCase).apply {
view = mockLoginView
}
#Test
public fun testLoginPresenterResume(){
given(mockLoginView.context()).willReturn(mockContext)
loginPresenter.resume();
// do actual assertions as what should happen
}
}
As usual you need to think about what you're testing. The scope of the test does not have to be limited to a single class. It's often easier to think of features you're testing instead of classes (like in BDD). Above all try to avoid tests like this - which in my opinion adds very little value as a regression test but still impedes refactoring.
PS. Roboelectric add helper functions for context