I'm new in testing and have a problem with a mock object. When I pass the call method that doesn't require any value the test is successful. But when I pass the value to the method and put it to JsonObject I have NPE. Why passed the argument to JsonObject lead to an error?
Class I want to test:
open class UserRepositoryImpl #Inject constructor(
private val movieApi: MovieApi
) : UserRepository {
override suspend fun createSession(requestToken: String): String {
val body = JsonObject().apply {
addProperty("request_token", requestToken) // error happen when I put argument to JsonObject
}
return movieApi.createSession(body = body) // NPE
.await()
.body()
?.getAsJsonPrimitive("session_id")
?.asString ?: ""
}
}
Test case:
class UserRepositoryImplTest {
#get:Rule
val mockitoRule: MockitoRule = MockitoJUnit.rule()
#Mock
lateinit var movieApi: MovieApi
#Mock
lateinit var localPrefStorage: LocalPrefStorage
lateinit var userRepository: UserRepositoryImpl
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
userRepository = UserRepositoryImpl(movieApi, localPrefStorage)
}
#Test
fun createSession() {
runBlocking {
val value = userRepository.createSession("request_token")
assertEquals(value, "")
}
}
}
You are mocking the movieApi and you should tell the mocked movieApi what to do if createSession method called
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
userRepository = UserRepositoryImpl(movieApi, localPrefStorage)
when(movieApi.createSession(body)).thenReturn(result)
}
Related
I am testing my Repository class using Mockito, specifically getProducts() functionality:
class Repository private constructor(private val retrofitService: ApiService) {
companion object {
#Volatile
private var INSTANCE: Repository? = null
fun getInstance(retrofitService: ApiService): Repository {
synchronized(this) {
var instance = INSTANCE
if (instance == null) {
instance = Repository(retrofitService)
}
INSTANCE = instance
return instance
}
}
}
suspend fun getProducts(): ProductsResponse = withContext(IO) {
retrofitService.getProducts()
}
}
This is my test class:
#ExperimentalCoroutinesApi
#RunWith(MockitoJUnitRunner::class)
class RepositoryTest {
// Class under test
private lateinit var repository: Repository
// Executes each task synchronously using Architecture Components.
#get:Rule
val instantExecutorRule = InstantTaskExecutorRule()
// Set the main coroutines dispatcher for unit testing.
#ExperimentalCoroutinesApi
#get:Rule
var mainCoroutineRule = MainCoroutineRule()
#Mock
private lateinit var retrofitService: ApiService
#Before
fun createRepository() {
MockitoAnnotations.initMocks(this)
repository = Repository.getInstance(retrofitService)
}
#Test
fun test() = runBlocking {
// GIVEN
Mockito.`when`(retrofitService.getProducts()).thenReturn(fakeProductsResponse)
// WHEN
val productResponse: ProductsResponse = repository.getProducts()
println("HERE = ${retrofitService.getProducts()}")
// THEN
println("HERE: $productResponse")
MatcherAssert.assertThat(productResponse, `is`(fakeProductsResponse))
}
}
And my ApiService:
interface ApiService {
#GET("https://www...")
suspend fun getProducts(): ProductsResponse
}
When I call repository.getProducts(), it returns null despite the fact, that I explicitly set retrofitService.getProducts() to return fakeProductsResponse, which is being called inside repository's getProducts() method. It should return fakeProductsResponse, but it returns null.
Am I doing wrong mocking or what the problem can be? Thanks...
EDIT: this is my MainCoroutineRule, if you need it
#ExperimentalCoroutinesApi
class MainCoroutineRule(val dispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()):
TestWatcher(),
TestCoroutineScope by TestCoroutineScope(dispatcher) {
override fun starting(description: Description?) {
super.starting(description)
Dispatchers.setMain(dispatcher)
}
override fun finished(description: Description?) {
super.finished(description)
cleanupTestCoroutines()
Dispatchers.resetMain()
}
}
It might not be a complete solution to your problem, but what I see is that your MainCoroutineRule overrides the mainDispatcher Dispatchers.setMain(dispatcher).
But in
suspend fun getProducts(): ProductsResponse = withContext(IO)
you are explicitely setting an IO Dispatcher.
I recommend always setting the dispatcher from a property you pass via constructor:
class Repository private constructor(
private val retrofitService: ApiService,
private val dispatcher: CoroutineDispatcher) {
companion object {
fun getInstance(retrofitService: ApiService,
dispatcher: CoroutineDispatcher = Dispatchers.IO): Repository {
// ommit code for simplicity
instance = Repository(retrofitService, dispatcher)
// ...
}
}
}
suspend fun getProducts(): ProductsResponse = withContext(dispatcher) {
retrofitService.getProducts()
}
}
Having it a default parameter you do not need to pass it in your regular code, but you can exchange it within your unit test:
class RepositoryTest {
private lateinit var repository: Repository
#get:Rule
var mainCoroutineRule = MainCoroutineRule()
#Mock
private lateinit var retrofitService: ApiService
#Before
fun createRepository() {
MockitoAnnotations.initMocks(this)
repository = Repository.getInstance(retrofitService, mainCoroutineRule.dispatcher)
}
}
For my own unit tests I am using the blocking function of TestCoroutineDispatcher from the CoroutineRule like:
#Test
fun aTest() = mainCoroutineRule.dispatcher.runBlockingTest {
val acutal = classUnderTest.callToSuspendFunction()
// do assertions
}
I hope this will help you a bit.
I'm new on unit testing. I'm trying to do unit testing on my view model class but my test fail with error:
Wanted but not invoked:
toggleMovieFavorite.invoke(
Movie(id=1, title=Title, overview=Overview, releaseDate=01/01/2025, posterPath=, backdropPath=, originalLanguage=ES, originalTitle=Title, popularity=5.0, voteAverage=7.0, favorite=false)
);
-> at xyz.jonthn.usescases.ToggleMovieFavorite.invoke(ToggleMovieFavorite.kt:7)
Actually, there were zero interactions with this mock.
This is my test file
#RunWith(MockitoJUnitRunner::class)
class DetailViewModelTest {
#get:Rule
val rule = InstantTaskExecutorRule()
#Mock
lateinit var findMovieById: FindMovieById
#Mock
lateinit var toggleMovieFavorite: ToggleMovieFavorite
#Mock
lateinit var observer: Observer<Movie>
private lateinit var vm: DetailViewModel
#ExperimentalCoroutinesApi
#Before
fun setUp() {
Dispatchers.setMain(Dispatchers.Unconfined)
vm = DetailViewModel(1, findMovieById, toggleMovieFavorite, Dispatchers.Unconfined)
}
#ExperimentalCoroutinesApi
#After
fun tearDown() {
Dispatchers.resetMain()
}
#Test
fun `when favorite clicked, the toggleMovieFavorite use case is invoked`() {
runBlocking {
val movie = mockedMovie.copy(id = 1)
whenever(findMovieById.invoke(1)).thenReturn(movie)
whenever(toggleMovieFavorite.invoke(movie)).thenReturn(movie.copy(favorite = !movie.favorite))
vm.movie.observeForever(observer)
vm.onFavoriteClicked()
verify(toggleMovieFavorite).invoke(movie)
}
}
val mockedMovie = Movie(
0,
"Title",
"Overview",
"01/01/2025",
"",
"",
"ES",
"Title",
5.0,
7.0,
false)
}
This is my DetailViewModel:
class DetailViewModel(
private val movieId: Int, private val findMovieById: FindMovieById,
private val toggleMovieFavorite: ToggleMovieFavorite,
uiDispatcher: CoroutineDispatcher) : ScopedViewModel(uiDispatcher) {
private val _movie = MutableLiveData<Movie>()
val movie: LiveData<Movie> get() = _movie
init {
launch {
_movie.value = findMovieById.invoke(movieId)
}
}
fun onFavoriteClicked() {
launch {
movie.value?.let {
_movie.value = toggleMovieFavorite.invoke(it)
}
}
}
}
And my use case ToggleMovieFavorite:
class ToggleMovieFavorite(private val moviesRepository: MoviesRepository) {
suspend fun invoke(movie: Movie): Movie = with(movie) {
copy(favorite = !favorite).also { moviesRepository.update(it) }
}
}
Thank you so much for your help guys!!!
i thougt mockito does not invoke your init method on viewmodel, you should put your declaration of vm on each #test instead of #Before since method findMovieById called at init, right before the function is mocked.
I have a repository class that uses a MutableLiveData object, exposed as just LiveData, to return results of async web queries to a ViewModel. The ViewModel then uses Transformation to map the results to another MutableLiveData which is observed by a View.
I think I followed the recommended architecture in this module by separating the concerns, but I find it hard to write unit tests for the ViewModel:
class DataRepository ( private val webservice: DataWebService ) {
private val _exception = MutableLiveData<Exception?>(null)
val exception : LiveData<Exception?> get() = _exception
private val _data = MutableLiveData<List<DataItem>>()
val data: LiveData<List<DataItem>> = _data
private val responseListener = Response.Listener<String> {response ->
try {
val list = JsonReader(SearchResult.mapping).readObject(response).map {
//Data transformation
}
_exception.value = null
_data.value = list
} catch (ex: Exception) {
_exception.value = ex
_data.value = emptyList()
}
}
fun findData(searchString: String) {
_data.value = emptyList()
webservice.findData(searchString, responseListener = responseListener)
}
}
class WebServiceDataViewModel (private val repository: DataRepository, app: App) : AndroidViewModel(app)
{
val dataList: LiveData<List<DataItem>> = Transformations.map(repository.data) {
_showEmpty.value = it.isEmpty()
it
}
val exception: LiveData<Exception?> get() = repository.exception
private val _showEmpty = MutableLiveData(true)
val showEmpty : LiveData<Boolean> = _showEmpty
private var _reloadOnCreate = true
var searchString: String? = null
set(value) {
field = value
if (!value.isNullOrBlank()) {
repository.findData(value)
}
}
}
ViewModel Test class:
#RunWith(JUnit4::class)
class WebServicePodcastViewModelTest {
#Rule var instantExecutorRule = InstantTaskExecutorRule()
#Mock lateinit var repository : DataRepository
#Mock lateinit var app : App
lateinit var viewModel: WebServiceDataViewModel
#Mock lateinit var exceptionObserver : Observer<Exception?>
#Mock lateinit var dataObserver : Observer<List<DataItem>>
#Mock lateinit var showEmptyObserver : Observer<Boolean>
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
viewModel = WebServiceDataViewModel(repository, app)
viewModel.exception.observeForever(exceptionObserver)
viewModel.showEmpty.observeForever(showEmptyObserver)
viewModel.dataList.observeForever(dataObserver)
}
#Test
fun searchForData() {
//given
val searchString = "MockSearch"
//when
`when`(repository.findData(searchString)).then { /* How to invoke the mock repositories LiveData? */ }
//then
//TODO: verify that ViewModel LiveData behaves as expected
}
}
So how do I invoke the immutable LiveData of a mocked class? Currently I'm trying to use Mockito & JUnit, but I'm open to different frameworks if the setup is easy!
In the end I ditched Mockito and used MockK instead which works like a charm!
You must take into consideration that you need to set and observe in the test the livedata which is observed from the view, then you can do something like this:
(As good practice, instead of having different Livedatas depending on the type od result, you can use a Sealed Class with generics to encapsulate your UI's state and to facilitate the process of observing different livedata rather than just one)
#RunWith(MockitoJUnitRunner::class)
class WebServicePodcastViewModelTest {
#Rule var instantExecutorRule = InstantTaskExecutorRule()
#Mock lateinit var repository : DataRepository
#Mock lateinit var app : App
lateinit var viewModel: WebServiceDataViewModel
#Mock lateinit var exceptionObserver : Observer<Exception?>
#Mock lateinit var dataObserver : Observer<List<DataItem>>
#Mock lateinit var showEmptyObserver : Observer<Boolean>
#Before
fun setUp() {
viewModel = WebServiceDataViewModel(repository, app)
viewModel.exception.observeForever(exceptionObserver)
viewModel.showEmpty.observeForever(showEmptyObserver)
viewModel.dataList.observeForever(dataObserver)
}
#Test
fun searchForData() {
//given
val searchString = "MockSearch"
val dataResponse = MutableLiveData<List<DataItem>>()
dataResponse.value = listOf<DataItem>()
//when
`when`(repository.findData(searchString)).then {
dataResponse
}
//then
assertNotNull(viewModel.getDataList().value)
assertEquals(viewModel.getDataList().value, emptyList()) /* empty list must be an object of the same type of listOf<DataItem>() */
}
}
I am using Junit & Mockito 4 for unit testing of viewModel.
ViewModel class
class MainViewModel(app: Application, private val githubRepo: GithubRepository) :
BaseViewModel(app) {
private val _trendingLiveData by lazy { MutableLiveData<Event<DataState<List<TrendingResponse>>>>() }
val trendingLiveData: LiveData<Event<DataState<List<TrendingResponse>>>> by lazy { _trendingLiveData }
var loadingState = MutableLiveData<Boolean>()
fun getTrendingData(language: String?, since: String?) {
launch {
loadingState.postValue(true)
when (val result = githubRepo.getTrendingListAsync(language, since).awaitAndGet()) {
is Result.Success -> {
loadingState.postValue(false)
result.body?.let {
Event(DataState.Success(it))
}.run(_trendingLiveData::postValue)
}
is Result.Failure -> {
loadingState.postValue(false)
}
}
}
}
}
Api EndPoinit
interface GithubRepository {
fun getTrendingListAsync(
language: String?,
since: String?
): Deferred<Response<List<TrendingResponse>>>
}
ViewModel Test class
#RunWith(JUnit4::class)
class MainViewModelTest {
#Rule
#JvmField
val instantTaskExecutorRule = InstantTaskExecutorRule()
#Mock
lateinit var repo: GithubRepository
#Mock
lateinit var githubApi: GithubApi
#Mock
lateinit var application: TrendingApp
lateinit var viewModel: MainViewModel
#Mock
lateinit var dataObserver: Observer<Event<DataState<List<TrendingResponse>>>>
#Mock
lateinit var loadingObserver: Observer<Boolean>
private val threadContext = newSingleThreadContext("UI thread")
private val trendingList : List<TrendingResponse> = listOf()
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
Dispatchers.setMain(threadContext)
viewModel = MainViewModel(application, repo)
}
#Test
fun test_TrendingRepo_whenSuccess() {
//Assemble
Mockito.`when`(githubApi.getTrendingListAsync("java", "daily"))
.thenAnswer{ return#thenAnswer trendingList.toDeferred() }
//Act
viewModel.trendingLiveData.observeForever(dataObserver)
viewModel.loadingState.observeForever(loadingObserver)
viewModel.getTrendingData("java", "daily")
Thread.sleep(1000)
//Verify
verify(loadingObserver).onChanged(true)
//verify(dataObserver).onChanged(trendingList)
verify(loadingObserver).onChanged(false)
}
#After
fun tearDown() {
Dispatchers.resetMain()
threadContext.close()
}
}
Problem is that my livedata is wrapped around Event<DataState<List<TrendingResponse>>, due to which I am not able to get what should be dataObserver and how should I verify that dataObserver in the test class.
Event os open class that is to handle event like SingleLiveData
DataState is sealed class that contain SUCCESS & FAILED data class
I have written test case livedata is like LiveData<List<Response> or something like that.
You need to wrap the List<TrendingResponse> → Event(DataState.Success(List<TrendingResponse>)) which you are returning using mockito - trendingList.toDeferred().
#Test
fun test_TrendingRepo_whenSuccess() {
//Assemble
Mockito.`when`(githubApi.getTrendingListAsync("java", "daily"))
.thenAnswer{ return#thenAnswer trendingList.toDeferred() }
//Act
viewModel.trendingLiveData.observeForever(dataObserver)
viewModel.loadingState.observeForever(loadingObserver)
viewModel.getTrendingData("java", "daily")
Thread.sleep(1000)
//Verify
verify(loadingObserver).onChanged(true)
//wrap the trendingList inside Event(DataState(YourList))
verify(dataObserver).onChanged(Event(DataState.Success(trendingList)))
verify(loadingObserver).onChanged(false)
}
I'm trying to learn Mockito on Android and using the mockito-kotlin library by nhaarman. I'm getting a strange Mockito error that reads:
"org.mockito.exceptions.misusing.CannotStubVoidMethodWithReturnValue:
'execute' is a void method and it cannot be stubbed with a return value"
The strange thing is that I'm not stubbing this class. Here's the unit test that's having the problem.
#RunWith(JUnit4::class)
class PoiListViewModelTest {
#get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
//Mocks
#Mock lateinit var getPois: GetPois
#Mock lateinit var mapper: PoiMapper
#Mock lateinit var observer: Observer<List<Poi_Presentation>>
// Class being tested
lateinit var poiListViewModel: PoiListViewModel
#Before
fun setup() {
MockitoAnnotations.initMocks(this)
poiListViewModel = PoiListViewModel(getPois,mapper)
}
private fun stubMapper(poi_Domain: Poi_Domain, poiPresentation: Poi_Presentation) {
`when`(mapper.mapToPresentation(poi_Domain)).thenReturn(poiPresentation)
}
#Test
fun fetchAllPoisExecutesUseCaseOnStart(){
verify(getPois, times(1)).execute(any(), eq(null))
}
#Test
fun poiSubscriberOnNextMapsCorrectly(){
val poiDomains = TestDataFactory.makePoiDomainList(2)
val poiPresentations = TestDataFactory.makePoiPresentationList(2)
stubMapper(poiDomains[0],poiPresentations[0])
poiListViewModel.getAllPois().observeForever(observer)
poiListViewModel.PoiSubscriber().onNext(poiDomains)
assertEquals(poiListViewModel.getAllPois().value,poiPresentations)
}
}
And here is my class being tested:
open class PoiListViewModel #Inject constructor(
private val getPois: GetPois,
private val mapper: PoiMapper
) : ViewModel() {
private val poiData: MutableLiveData<List<Poi_Presentation>> = MutableLiveData()
init {
fetchAllPois()
}
fun getAllPois(): LiveData<List<Poi_Presentation>> {
return poiData
}
fun fetchAllPois() {
getPois.execute(PoiSubscriber())
}
override fun onCleared() {
getPois.dispose()
super.onCleared()
}
inner class PoiSubscriber: DisposableObserver<List<Poi_Domain>>(){
override fun onComplete() {
Log.i("poiSubscriber: ", "completed")
}
override fun onNext(data: List<Poi_Domain>) {
poiData.postValue(data.map {
mapper.mapToPresentation(it)
})
}
override fun onError(e: Throwable) {
Log.e("Obervable error: ", e.localizedMessage )
}
}
}
It's a short unit test, and you can see that I'm not trying to stub execute, so why is Mockito giving me this error? the execute method does fire when my test class get's created, but I used a mock object (getPois), so the method should do nothing by default correct? Strangely the error occurs when I attempt to stub another class PoiMapper. Here's that class:
open class PoiMapper #Inject constructor() {
fun mapToPresentation(domainModel: Poi_Domain): Poi_Presentation {
return Poi_Presentation(domainModel.id,domainModel.name,domainModel.description,
domainModel.img_url,domainModel.latitude,domainModel.longitude,domainModel.imgFocalpointX,
domainModel.imgFocalpointY,domainModel.collection,domainModel.collectionPosition,
domainModel.release,domainModel.stampText)
}
}
This method does have a return type, so it should not be a part of the problem. I'm hoping someone can explain to me what's going on as I'm stumped. Does it have something to do with the fact that I'm calling this method in a lambda?