How to use mockito to test retrofit2 response? - unit-testing

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()
}
}

Related

Access ViewModel inside Testcase using Hilt

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.

Unit test with Flow and Transformations.map

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

Unit testing and high-order functions in Kotlin

So i have a base class:
abstract class BaseService<M : MyMetric>(private val otherService: OtherService) {
abstract fun <T> monitor(metric: M, action: () -> T): T
protected fun <T> monitorDefault(metric: M, action: () -> T): T {
val begin = System.currentTimeMillis()
return try {
notifyEvent(metric.total)
val result = action()
notifyEvent(metric.success)
result
} catch (e: CommonException) {
//handling
}
}
and simple implementation:
class PrintMetricService(otherService: OtherService): BaseService<PrintMetric>(otherService) {
override fun <T> monitor(metric: PrintMetric, action: () -> T) : T {
return monitorDefault(metric, action)
}
}
then i use this method:
#Service
class PrintReportService(private val reportService: ReportService,
private val metricService: MetricService<PrintMetric>) {
fun getReport(): String {
return metricService.monitor(PrintMetric) {
val response = try {
reportService.renderPdf(jsonRequest, TEMPLATE_CODE, channel)
} catch (e: Exception) {
throw CommonException(PRINT_SERVICE_ERROR_EXCEPTION_CODE)
}
response.body.data!!
}
}
now I want to write a unit test for getReport() (we use TestNg and mockito in project):
class PrintReportServiceImplTest {
#InjectMocks
private lateinit var printReportService: PrintReportServiceImpl
#Mock
private lateinit var reportService: ReportService
#Mock
private lateinit var metricService: PrintMetricService
#BeforeMethod
fun setUp() {
openMocks(this)
whenever(reportService.renderPdf().thenReturn("response (base64)")
}
#Test
fun getReportTest() {
val report = printReportService.getReport()
assertNotNull(report)
}
}
the test crashes with an error: java.lang.AssertionError: expected object to not be null
But if i add inline when override monitor(metric: M, action: () -> T) in PrintMetricService the test will pass. What could be the problem? Is this a feature of the high-order functions in Kotlin?

mockk, how to use slot for MutableMap<String, String>

using mockk 1.9.3
having a function to be verified
class EventLogger private constructor()
fun logUserEvent(eventName: String?, eventParamMap: MutableMap<String, String>?) {
......
internaLogEventImpl(eventName, eventParamMap)
}
internal fun internaLogEventImpl(eventName: String?, customParams: MutableMap<String, String>?) {
......
}
companion object {
#Volatile
private var sEventLoggerSingleton: EventLogger? = null
#JvmStatic
val instance: EventLogger
get() {
if (sEventLoggerSingleton == null) {
sEventLoggerSingleton = EventLogger()
}
return sEventLoggerSingleton!!
}
}
got compiler error at every {eventLogger.internaLogEventImpl(any(), mapSlot)}
Type mismatch.
Required: MutableMap<String, String>?
Found: CapturingSlot<MutableMap<String, String>>
when trying this below :
class TestK {
lateinit var eventLogger: EventLogger
lateinit var application: Application
val mapSlot = slot<MutableMap<String, String>>()
#Before
fun setUp() {
application = ApplicationProvider.getApplicationContext<Application>()
eventLogger = mockk.spyk(EventLogger.instance)
ReflectionHelpers.setStaticField(EventLogger::class.java, "sEventLoggerSingleton", eventLogger)
}
#After
fun cleanUp() {
ReflectionHelpers.setStaticField(EventLogger::class.java, "sEventLoggerSingleton", null)
}
#Test
fun logNotificationStatusChange_with_enabled_WhenCalled_ShouldLog() {
val testMap = hashMapOf("action" to "open")
every {eventLogger.internaLogEventImpl(any(), mapSlot)} answers {
println(mapSlot.captured)
assert(mapSlot.captured["action"] == "open")
}
eventLogger.logUserEvent("test_event", testMap)
}
}
You need to use capture (see Mockk's Capturing section).
So for your case capture(mapSlot) should work.
eventLogger.internaLogEventImpl(any(), capture(mapSlot))
Before trying to mock on complex code, It would be better to learn on easier example.
Here is a working example to mock a private call on an object with mockk.
object MyLogger {
fun logUserEvent(event: String?, map: MutableMap<String, String>?) {
// turns the event string into an uppercase string.
internaLogEventImpl(event?.toUpperCase(), map)
}
private fun internaLogEventImpl(event: String?, map: MutableMap<String, String>?): Unit =
throw Exception("real implementation")
}
How to test and mock the internal function so we don't throw the exception.
#Test
fun `test logger internal`() {
val expectedMap = mutableMapOf("a" to "b")
val expectedEvent = "EVENT"
val mock = spyk(MyLogger, recordPrivateCalls = true)
justRun { mock["internaLogEventImpl"](expectedEvent, expectedMap) }
// or justRun { mock["internaLogEventImpl"](any<String>(), any<MutableMap<String, String>>()) }
mock.logUserEvent("event", expectedMap)
verify { mock["internaLogEventImpl"](expectedEvent, expectedMap) }
}
Here logUserEvent calls the real implementation and internaLogEventImpl calls the mock implementation.
if justRun { mock["internaLogEventImpl"](expectedEvent, expectedMap) } is not called (or wrong because the argument doesn't match), the real implementation will be call. Here it will throw Exception("real implementation").
Please try and modify values to check different behaviors.

How To Test Object expression methods by mocking external dependency which its mothod is using?

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())
}
}