I'm working on unit tests and I have trouble with Flow and Transformations.map.
I think this might be a problem of observer but I'm not sure.
This is the functions of my ViewModel I would like to test
val allPropertiesLiveData: LiveData<List<MapsViewStateItem>> =
Transformations.map(propertyRepository.getAllPropertiesComplete().asLiveData(), ::filterProperty)
private fun filterProperty(properties: List<PropertyWithProximity>?): List<MapsViewStateItem> {
val newList = mutableListOf<MapsViewStateItem>()
properties?.forEach { p ->
if (p.property.dateSold == null)
newList.add(
MapsViewStateItem(
p.property.idProperty,
p.typeOfProperty.nameType,
p.property.price,
p.photos[0],
p.property.adress
)
)
}
return newList
}
However, in the test, I can't get any value for the resulting liveData
This is my test class
class MapsViewModelTest2 {
private val testDispatcher = StandardTestDispatcher()
lateinit var viewModel: MapsViewModel
#Mock
lateinit var propertyRepository: PropertyRepository
#Mock
lateinit var navigationRepository: NavigationRepository
#get:Rule
var instantTaskExecutorRule: InstantTaskExecutorRule = InstantTaskExecutorRule()
#Mock
private lateinit var mockObserver: Observer<List<MapsViewStateItem>>
#Before
fun setUp() {
Dispatchers.setMain(testDispatcher)
MockitoAnnotations.openMocks(this)
}
#Test
fun getAllPropertiesLiveData2() = runTest {
viewModel = MapsViewModel(propertyRepository, navigationRepository)
val flow = flow {
emit(FakeDatas.fakePropertiesCompletes)
}
Mockito.`when`(propertyRepository.getAllPropertiesComplete()).thenReturn(flow)
viewModel.allPropertiesLiveData.observeForever(mockObserver)
assertEquals(FakeDatas.fakePropertiesCompletes.size,
viewModel.allPropertiesLiveData.value?.size)
viewModel.allPropertiesLiveData.removeObserver(mockObserver)
}
}
And the error is
expected:<2> but was:<null>
Expected :2
Actual :null
Related
With mockito, I want to test a retrofit response.
Can you guide me in this..
I am new in unit testing with mockito so if you can guide me try to explain your answer a little bit.
viewModel
fun loginUser() {
repository.loginUser(this, "john#mail.com", "changeme2")
}
Repository class
open class LoginRepository #Inject constructor() {
fun loginUser(loginResponse: LoginResponse, email: String, password: String) {
val result = RetrofitClient.getAPI(RetrofitClient.getInstance())
var call = result.loginUser(email, password)
call.enqueue(object : Callback<JsonObject> {
override fun onResponse(call: Call<JsonObject>, response: Response<JsonObject>) {
loginResponse.onSuccess()
}
override fun onFailure(call: Call<JsonObject>, t: Throwable) {
loginResponse.onFail()
}
})
}
}
Test class
#RunWith(MockitoJUnitRunner::class)
class LoginViewModelMockTest {
lateinit var loginViewModel: LoginViewModel
lateinit var repository: LoginRepository
lateinit var loginResponse: LoginResponse
#Before
fun setUp() {
loginResponse=mock(LoginResponse::class.java)
repository = mock(LoginRepository::class.java)
loginViewModel = LoginViewModel(repository)
}
#Test
fun loginUser_loginUserCalled_onSuccessShouldCalled() {
loginViewModel.loginUser()
`when`(repository.loginUser(loginResponse, "john#mail.com", "changeme2")).then{
(it.getArgument(2) as LoginResponse).onSuccess()
}
verify(loginResponse).onSuccess()
}
}
Can anyone suggest me that how can I access ViewModel inside Test case using Hilt?
ViewModel class:
#HiltViewModel
class BaseViewModel #Inject constructor(private val repository: BaseRepository) : ViewModel()
AppModule for Hilt
#Module
#InstallIn(SingletonComponent::class)
object AppModule {
#Provides
fun provideBaseApi(
remoteDataSource: RemoteDataSource
): BaseApi {
return remoteDataSource.buildApi(BaseApi::class.java)
}
Test case file
#SmallTest
#HiltAndroidTest
class BaseViewModelTest {
#get:Rule(order = 0)
val hiltRule = HiltAndroidRule(this)
#get:Rule(order = 1)
val activityRule = ActivityScenarioRule(MainActivity::class.java)
#Inject
lateinit var baseRepository: BaseRepository
#BindValue
#JvmField
var viewModel = mockk<BaseViewModel>(relaxed = true)
#Before
fun init(){
hiltRule.inject()
}
It is not giving any access to mutable data of ViewModel class.
Thank you in advance.
I am unit testing the following class
class LoadTrendingSearchUseCaseImp #Inject constructor(
private val searchCriteriaProvider: SearchCriteriaProvider,
private val coroutineDispatcherProvider: CoroutineDispatcherProvider
) : LoadTrendingSearchUseCase {
override suspend fun execute(): List<String> {
return withContext(coroutineDispatcherProvider.io()) {
searchCriteriaProvider.provideTrendingSearch().trendingSearches
}
}
}
interface SearchCriteriaProvider {
suspend fun provideTrendingSearch(): CatalogSearchPage
}
class SearchCriteriaProviderImp() : SearchCritieraProvider {
override suspend fun provideTrendingSearch(): CatalogSearchPage {
return withContext(coroutineDispatcherProvider.io()) {
/* long running task */
}
}
}
interface CoroutineDispatcherProvider {
fun io(): CoroutineDispatcher = Dispatchers.IO
fun default(): CoroutineDispatcher = Dispatchers.Default
fun main(): CoroutineDispatcher = Dispatchers.Main
fun immediate(): CoroutineDispatcher = Dispatchers.Main.immediate
fun unconfined(): CoroutineDispatcher = Dispatchers.Unconfined
}
class CoroutineDispatcherProviderImp #Inject constructor() : CoroutineDispatcherProvider
This is my actual test:
class LoadTrendingSearchUseCaseImpTest {
private val searchCriteriaProvider: SearchCriteriaProvider = mock()
private val coroutineDispatcherProvider = CoroutineDispatcherProviderImp()
private lateinit var loadTrendingSearchUseCaseImp: LoadTrendingSearchUseCaseImp
#Before
fun setUp() {
loadTrendingSearchUseCaseImp = LoadTrendingSearchUseCaseImp(
searchCriteriaProvider,
coroutineDispatcherProvider
)
}
#Test
fun `should provide trending searches`() {
runBlockingTest {
// Arrange
// EXCEPTION HERE whenever(searchCriteriaProvider.provideTrendingSearch().trendingSearches).thenReturn(
emptyList()
)
// Act
val actualResult = loadTrendingSearchUseCaseImp.execute()
// Assert
assertThat(actualResult).isEmpty()
}
}
}
The actual error message:
java.lang.NullPointerException
.product_search.usecase.imp.LoadTrendingSearchUseCaseImpTest$should provide trending searches$1.invokeSuspend(LoadTrendingSearchUseCaseImpTest.kt:30)
You tried to chain invocations when stubbing a method.
whenever(searchCriteriaProvider.provideTrendingSearch().trendingSearches)
.thenReturn(emptyList())
During stubbing, the actual methods are being called.
searchCriteriaProvider.provideTrendingSearch() returns null, as this call is not stubbed yet
subsequent call null.trendingSearches results in NPE
You need to stub each call in the chain
whenever(searchCriteriaProvider.provideTrendingSearch())
.thenReturn(catalogSearchPage)
whenever(catalogSearchPage.trendingSearches)
.thenReturn(emptyList())
Obviously, this assumes that
catalogSearchPage is also a mock
trendingSearches is a property
Alternatively, you can construct a POJO for catalogSearchPage, and return it in the first stubbing.
While writing unit tests, #InjectMock creates an instance of the class and injects the mocked instances defined using #Mock.
So this works fine when we are testing methods of a class. I have a problem while testing methods of an object expression.
For example:
I have an object class DbService.
object DbService {
private lateinit var connection: Connection
init {
makeConnection()
}
private fun makeConnection(){
Class.forName("com.mysql.cj.jdbc.Driver")
try{
connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/Users", "user", "password")
}catch (ex: Exception){
println(ex)
}
}
fun checkConnection(){
var preparedStatement: PreparedStatement = connection.prepareStatement("SHOW DATABASES;")
var resultSet = preparedStatement.executeQuery()
while(resultSet.next()){
println(resultSet.getObject(1))
}
}
}
Now I have to test checkConnection function. How can I do so?
Actually doing some more research, I got some hints and it worked for me.
It's like, you can do manual dependency injection using the setter method.
setter-based Dependency Injection
so what I did, I added one more function to manually set the connection object like this.
object DbService {
private lateinit var connection: Connection
init {
makeConnection()
}
private fun makeConnection(){
Class.forName("com.mysql.cj.jdbc.Driver")
try{
connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/Users", "user", "password")
}catch (ex: Exception){
println(ex)
}
}
// setter based dependency injection <----
fun initializeConnectionObject(conn: Connection){
connection = conn
}
fun printingSomeMoreData(){
var statement: Statement
var resultSet: ResultSet
var preparedStatement: PreparedStatement = connection.prepareStatement("SELECT * FROM titles where title=?;")
preparedStatement.setString(1, "Engineer")
resultSet = preparedStatement.executeQuery()
val columnCount = resultSet.metaData.columnCount
while(resultSet.next()){
for(i in 1..columnCount){
print(resultSet.getObject(i).toString() + " ")
}
println()
}
}
}
with the tests like:
#RunWith(MockitoJUnitRunner::class)
class DbServiceTest {
private lateinit var dbService: DbService
#Mock lateinit var mockedConnection: Connection
#Mock lateinit var mockedPreparedStatement: PreparedStatement
#Mock lateinit var mockedResultSet: ResultSet
#Mock
lateinit var mockedResultSetMetaData: ResultSetMetaData
#Before
fun setUp(){
MockitoAnnotations.initMocks(true)
}
#Test
fun printingSomeMoreDataTest() {
dbService = DbService
Mockito.`when`(mockedResultSetMetaData.columnCount).thenReturn(3)
Mockito.`when`(mockedResultSet.next()).thenReturn(true, true, true, true, false)
Mockito.`when`(mockedResultSet.metaData).thenReturn(mockedResultSetMetaData)
Mockito.`when`(mockedResultSet.getObject(1)).thenReturn("11", "21", "31", "41")
Mockito.`when`(mockedResultSet.getObject(2)).thenReturn("12", "22", "32", "42")
Mockito.`when`(mockedResultSet.getObject(3)).thenReturn("13", "23", "33", "43")
Mockito.`when`(mockedPreparedStatement.executeQuery()).thenReturn(mockedResultSet)
Mockito.`when`(mockedConnection.prepareStatement(Mockito.anyString())).thenReturn(mockedPreparedStatement)
dbService.initializeConnectionObject(mockedConnection) // <----
dbService.printingSomeMoreData()
Mockito.verify(mockedResultSet, times(12)).getObject(anyInt())
}
}
I would like to test case when server does not return response, and we trigger the next network call ( like for example search query).
So we basically have a method inside ViewModel and Retrofit method
interface RetrofitApi {
#GET("Some Url")
suspend fun getVeryImportantStuff(): String
}
class TestViewModel(private val api: RetrofitApi) : ViewModel() {
private var askJob: Job? = null
fun load(query: String) {
askJob?.cancel()
askJob = viewModelScope.launch {
val response = api.getVeryImportantStuff()
//DO SOMETHING WITH RESPONSE
}
}
}
And I would like to test case when new query is asked, and the old one didn't returns.
for case when response returns test is easy
#Test
fun testReturnResponse() {
runBlockingTest {
//given
val mockApi:RetrofitApi = mock()
val viewModel = TestViewModel(mockApi)
val response = "response from api"
val query = "fancy query"
whenever(mockApi.getVeryImportantStuff()).thenReturn(response)
//when
viewModel.load(query)
//then
//verify what happens
}
}
But I don't know how to mock suspend function that did't come back, and test case when new request is triggered like this
#Test
fun test2Loads() {
runBlockingTest {
//given
val mockApi:RetrofitApi = mock()
val viewModel = TestViewModel(mockApi)
val response = "response from api"
val secondResponse = "response from api2"
val query = "fancy query"
whenever(mockApi.getVeryImportantStuff())
.thenReturn(/* Here return some fancy stuff that is suspend* or something like onBlocking{} stub but not blocking but dalayed forever/)
.thenReturn(secondResponse)
//when
viewModel.load(query)
viewModel.load(query)
//then
//verify that first response did not happens , and only second one triggered all the stuff
}
}
Any ideas ?
EDIT: I'm not really attached to mockito, any mock library will be good :)
regards
Wojtek
I came up with kind of solution to the problem, but slightly different than I was thinking at the beginning
interface CoroutineUtils {
val io: CoroutineContext
}
interface RetrofitApi {
#GET("Some Url")
suspend fun getVeryImportantStuff(query: String): String
}
class TestViewModel(private val api: RetrofitApi,
private val utils: CoroutineUtils) : ViewModel() {
private val text = MutableLiveData<String>()
val testStream: LiveData<String> = text
private var askJob: Job? = null
fun load(query: String) {
askJob?.cancel()
askJob = viewModelScope.launch {
val response = withContext(utils.io) { api.getVeryImportantStuff(query) }
text.postValue(response)
}
}
}
And the test scenario would look like this
class TestViewModelTest {
#get:Rule
val coroutineScope = MainCoroutineScopeRule()
#get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
lateinit var retrofit: RetrofitApi
lateinit var utils: CoroutineUtils
val tottalyDifferentDispatcher = TestCoroutineDispatcher()
lateinit var viewModel: TestViewModel
#Before
fun setup() {
retrofit = mock()
utils = mock()
viewModel = TestViewModel(retrofit, utils)
}
#UseExperimental(ExperimentalCoroutinesApi::class)
#Test
fun test2Loads() {
runBlockingTest {
//given
val response = "response from api"
val response2 = "response from api2"
val query = "fancy query"
val query2 = "fancy query2"
whenever(utils.io)
.thenReturn(tottalyDifferentDispatcher)
val mutableListOfStrings = mutableListOf<String>()
whenever(retrofit.getVeryImportantStuff(query)).thenReturn(response)
whenever(retrofit.getVeryImportantStuff(query2)).thenReturn(response2)
//when
viewModel.testStream.observeForever {
mutableListOfStrings.add(it)
}
tottalyDifferentDispatcher.pauseDispatcher()
viewModel.load(query)
viewModel.load(query2)
tottalyDifferentDispatcher.resumeDispatcher()
//then
mutableListOfStrings shouldHaveSize 1
mutableListOfStrings[0] shouldBe response2
verify(retrofit, times(1)).getVeryImportantStuff(query2)
}
}
}
It is not exactly what I wanted, because retrofit call is not triggered when load method is called for the first time, but it is the closest solution.
What would be a perfect test for me will be assertion that retrofit was called twice , but only the second one returned to ViewModel. Solution for that will be to wrap Retrofit around method that returns suspend function like this
interface RetrofitWrapper {
suspend fun getVeryImportantStuff(): suspend (String)->String
}
class TestViewModel(private val api: RetrofitWrapper,
private val utils: CoroutineUtils) : ViewModel() {
private val text = MutableLiveData<String>()
val testStream: LiveData<String> = text
private var askJob: Job? = null
fun load(query: String) {
askJob?.cancel()
askJob = viewModelScope.launch {
val veryImportantStuff = api.getVeryImportantStuff()
val response = withContext(utils.io) {
veryImportantStuff(query)
}
text.postValue(response)
}
}
}
and test for it
#Test
fun test2Loads() {
runBlockingTest {
//given
val response = "response from api"
val response2 = "response from api2"
val query = "fancy query"
val query2 = "fancy query2"
whenever(utils.io)
.thenReturn(tottalyDifferentDispatcher)
val mutableListOfStrings = mutableListOf<String>()
whenever(retrofit.getVeryImportantStuff())
.thenReturn(suspendCoroutine {
it.resume { response }
})
whenever(retrofit.getVeryImportantStuff()).thenReturn(suspendCoroutine {
it.resume { response2 }
})
//when
viewModel.testStream.observeForever {
mutableListOfStrings.add(it)
}
tottalyDifferentDispatcher.pauseDispatcher()
viewModel.load(query)
viewModel.load(query2)
tottalyDifferentDispatcher.resumeDispatcher()
//then
mutableListOfStrings shouldHaveSize 1
mutableListOfStrings[0] shouldBe response2
verify(retrofit, times(2)).getVeryImportantStuff()
}
}
But in my opinion it is a little bit too much in interference in code only to be testable. But maybe I'm wrong :P
Looks like you want to test scenario when you have unreachable server, timeout or something similar.
In this case while doing your mock you can say that on first try it returns object and then on second executions throws exception like java.net.ConnectException: Connection timed out.
whenever(mockApi.getVeryImportantStuff())
.thenReturn(someObjet)
.thenThrow(ConnectException("timed out"))
And this this should work but you will have to do try/catch block in ViewModel witch is not ideal. I would suggest you to add additional abstraction.
You could you Repository or UseCase or whatever pattern/name you like to move the network call there. Then introduce sealed class Result to encapsulate behaviour and make your ViewModel more readable.
class TestViewModel(val repo: Repo): ViewModel() {
private var askJob: Job? = null
fun load(query: String) {
askJob?.cancel()
askJob = viewModelScope.launch {
when (repo.getStuff()) {
is Result.Success -> TODO()
is Result.Failure -> TODO()
}
}
}
}
class Repo(private val api: Api) {
suspend fun getStuff() : Result {
return try {
Result.Success(api.getVeryImportantStuff())
} catch (e: java.lang.Exception) {
Result.Failure(e)
}
}
}
sealed class Result {
data class Success<out T: Any>(val data: T) : Result()
data class Failure(val error: Throwable) : Result()
}
interface Api {
suspend fun getVeryImportantStuff() : String
}
With that level of abstraction your ViewModelTest only checks what happens in two cases.
Hope that's helpful!