I am writing unit tests and I just can't still find place to use Mockito. For example I have test where I use MockWebServer to mock response. Here is some code, will give you only code of 1 test not to bother you with whole before/after preparation etc..:
#Test
fun `check successful response`() {
val configurationResponse = Mockito.mock(ConfigurationResponse::class.java)
val jsonInString: String = Gson().toJson(configurationResponse)
val response = MockResponse()
.setBody(jsonInString)
.setResponseCode(HttpURLConnection.HTTP_OK)
mockWebServer.enqueue(response)
runBlocking {
val responseResult = restService.getConfiguration()
assertTrue(responseResult is ResultWrapper.Success)
responseResult.getResult(
success = {
assertEquals(
it,
configurationResponse
) //THIS ASSERT GIVES FALSE
}
)
}
}
As you can see I wrote the problem. My assert gives me false. I found out that I can not assert/compare mocked objects with other ones, since it will always give me false, right?
Is there any workaround to accomplish this, it looks very powerful to me if it is doable.
Related
I am making an api call using retrofit and I want to write a unit test to check if it returns an exception.
I want to force the retrofit call to return an exception
DataRepository
class DataRepository #Inject constructor(
private val apiServiceInterface: ApiServiceInterface
) {
suspend fun getCreditReport(): CreditReportResponse {
try {
val creditReport = apiServiceInterface.getDataFromApi() // THIS SHOULD RETURN AN EXCEPTION AND I WANT TO CATCH THAT
return CreditReportResponse(creditReport, CreditReportResponse.Status.SUCCESS)
} catch (e: Exception) {
return CreditReportResponse(null, CreditReportResponse.Status.FAILURE)
}
}
}
ApiServiceInterface
interface ApiServiceInterface {
#GET("endpoint.json")
suspend fun getDataFromApi(): CreditReport
}
I have written a test case for getCreditReport which should validate the failure scenario
#Test
fun getCreditReportThrowException() {
runBlocking {
val response = dataRepository.getCreditReport()
verify(apiServiceInterface, times(1)).getDataFromApi()
Assert.assertEquals(CreditReportResponse.Status.FAILURE, response.status)
}
}
so to make the above test case pass, I need to force the network call to throw and exception
please suggest
Thanks
R
Actually #Vaibhav Goyal provided a good suggestion to make your testing as easier. Assuming you are using MVVM structure, in your test cases you can inject a "mock" service class to mock the behaviours that you defined in the test cases, so the graph will be like this
Since I am using mockk library at the moment, the actual implementation in your code base would be a little bit different.
#Test
fun test_exception() {
// given
val mockService = mockk<ApiServiceInterface>()
val repository = DataRepository(mockService)
every { mockService.getDataFromApi() } throws Exception("Error")
// when
val response = runBlocking {
repository.getCreditReport()
}
// then
verify(exactly = 1) { mockService.getDataFromApi }
assertEquals(CreditReportResponse.Status.FAILURE,response.status)
}
But if you want to test the exception thrown from Retrofit, then you might need mockServer library from square to help you to achieve this https://github.com/square/okhttp#mockwebserver
And the graph for this would be like this
You also have to setup the mock server to do so
#Test
fun test_exception_from_retrofit() {
// can put in the setup method / in junit4 rule or junit5 class
val mockWebServer = MockWebServer()
mockWebServer.start()
// given
val service = Retrofit.Builder()
.baseUrl(mockWebServer.url("/").toString())
.build()
.create(ApiServiceInterface::class)
val repository = DataRepository(service)
// when
mockWebServer.enqueue(MockResponse()
.setResponseCode(500)
.setBody("""{"name":"Tony}""") // you can read the json file content and then put it here
)
val response = runBlocking {
repository.getCreditReport()
}
// then
verify(exactly = 1) { mockService.getDataFromApi }
assertEquals(CreditReportResponse.Status.FAILURE,response.status)
// can put in tearDown / in junit4 rule or juni5 class
mockWebServer.shutdown()
}
SO you can test different exception like json format invalid, 500 status code,data parsing exception
Bonus point
Usually I would put the testing json under test directory and make it almost same as the api path for better maintainence
I have a coroutine in my viewModel which runs perfectly fine. When I try to unit test the same, it throws the following error "Could not create instance for [type:Factory,primary_type:..MyService"
I am injecting a service and making an API call which works fine while unit testing. If the API fails, I am retrying the same API call with a new instance of Service with different parameters. This works fine in my application but fails in unit test. Here is the following code:
coroutineScope.launch {
try {
var getResponse = myApi?.getCodeApi()
if (getResponse?.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
// Retrying with instance of service with a different token
val newMyApiService: MyService? by inject { parametersOf(newToken) }
getResponse = newMyApiService?.getCodeApi()
}
checkResponse(getResponse)
} catch (exception: Exception) {
Timber.e(exception)
}
}
Is there a way to fix this?. I have taken all the required measures like startingKoinApp for the test environment, also included the required Koin modules before starting to test.
A part of unit test looks something like this
whenever(myAPi.getCodeApi()).thenReturn(properResponse)
val errorResponse : Response<DataModel> = mock()
whenever(response.code()).thenReturn(HttpsURLConnection.HTTP_UNAUTHORIZED)
whenever(myAPi.getCodeApi()).thenReturn(errorResponse)
This can be fixed by replacing the line
val newMyApiService: MyService? by inject { parametersOf(newToken) }
with
val newMyApiService: getNewService(newToken)
The new method will be
fun getNewService(newToken: String): MyService? {
return MyService? by inject { parametersOf(newToken) }
}
Now within your unit test, you can mock the method using power mockito
val underTestsSpy = PowerMockito.spy(underTests)
PowerMockito.doReturn(myserviceApi).`when`(underTestsSpy,
"getNewService", newToken)
Through this, you can mock a new service instance that is created within the method being tested
I have a Client that will make a request to a GraphQL end point as below
import com.apollographql.apollo.ApolloClient
internal class GraphQLClient(apolloClient:ApolloClient, retryStrategy:RetryStrategy){
override fun <D : Operation.Data, T, V : Operation.Variables> mutate(mutate: Mutation<D, T, V>): Flow<T> {
val response = apolloClient.mutate(mutate)
val responseFlow = response.toFlow()
return responseFlow.map<Response<T>, T> {
it.data ?: throw ResponseError("Data Null")
}.retryWhen { cause, attempt ->
retryStrategy.retry(cause,attempt)
}
}
}
I'm testing the above using MockWebServer to create mock responses
JUnit test
Using Turbine to test Flow
I'm trying to validate that for a successful update request the retry logic doesn't get executed
#Test
fun `GIVEN a successful update, THEN don't retry`() = runBlocking {
val server = MockWebServer()
val mockResponse = MockResponse()
//Successful response in json format. It's the correct format.
mockResponse.setBody(readFileFromResources("mock_success_response.json"))
mockResponse.setResponseCode(200)
server.enqueue(mockResponse)
server.start()
val url = server.url("http://loalhost:8080")
val apolloClient: ApolloClient = ApolloClient.builder()
.okHttpClient(OkHttpClient())
.serverUrl(url.toString())
.addCustomAdapters()
.build()
val retryStrategy = mockk<RetryStrategy>()
val graphQLClient = GraphQLClient(apolloClient)
//The mutation of intrest
val mutation = UpdateMutation(
SomeInput(
"123"
)
)
//note how i haven't mocked anything related to retry strategy cause this test doesn't need that
graphQLClient.mutate(mutation).test {
verify(exactly = 0) { retryStrategy.retry(any(),any()) }
expectComplete()
}
server.shutdown()
}
However, my test fails with
app.cash.turbine.AssertionError: Expected complete but found Error(MockKException)
Further down the stack trace, I can see the complaint over the lack of answer for some re-try logic related things
But i think that's a cause of the above exception being thrown and in reality, shouldn't even be executed
Caused by: io.mockk.MockKException: no answer found for: RetryStrategy(#1).isError(com.apollographql.apollo.exception.ApolloNetworkException: Failed to execute http call)
P:S- I'm probably testing a little too much here too but keen to understand what's going on
Things I've tried
Just changed the response to an empty string if that has an impact but no change in error. Which makes me think it probably has nothing to do with the response data,
Thanks
The problem was with this line
val url = server.url("http://loalhost:8080")
MockServer is not expecting host or port
val url = server.url("/somepath")
The error I have:
The code with the error:
#RunWith(PowerMockRunner::class)
#PrepareForTest(PotatoProvider::class, PotatoConsumer::class)
class WantedButNotInvoked {
#Mock
lateinit var potatoConsumer: PotatoConsumer
#Test
fun potato() {
Observable.just(Potato()).subscribe(potatoConsumer)
verify(potatoConsumer).accept(Potato())
//verify(potatoConsumer).accept(any()) //-> This fails too with the same reason
}
}
data class Potato(val value: Int = 1)
class PotatoConsumer : Consumer<Potato> {
override fun accept(t: Potato?) {
println(t)
}
}
So I making subscribe with this mock(potatoConsumer), and the rxJava have called 'accept', and mockito mark it as interaction, but mockito thinks this interaction is not what I'm expecting, why?
Versions of libraries used her:
mockitoVersion = '2.8.9'
mockitoAndroidVersion = '2.7.22'
powerMockVersion="2.0.2"
kotlinMockito="2.1.0"
rxKotlin = "2.3.0"
rxJavaVersion = "2.2.10"
Kinda workaround
Some fields mocked by powermock, fails on 'verify', but fields mocked with mockito is not;
Mockito can't mock not opened fields, without mock-maker-inline, but mockito conflicts with Powermock mock-maker-inline;
Powermock can delegate calls of mock-maker-inline to other mock-maker-inline(https://github.com/powermock/powermock/wiki/PowerMock-Configuration)
Use Mockito.mock on the failed fields instead of #Mock/Powermock mock injection
Example of the "green" potato test method using PowerMockRunner
#Test
fun potato() {
potatoConsumer = mock() // <-
Observable.just(Potato()).subscribe(potatoConsumer)
verify(potatoConsumer).accept(potato)
}
I am not familiar with PowerMock but I tried this test and it passes:
#Test
fun potato() {
fakePotatoProvider = Mockito.mock(PotatoProvider::class.java)
potatoConsumer = Mockito.mock(PotatoConsumer::class.java)
`when`(fakePotatoProvider.getObservable()).thenReturn(Observable.just(Potato()))
fakePotatoProvider.getObservable().subscribe(potatoConsumer)
verify(potatoConsumer).accept(Potato())
}
Maybe because you aren't passing the same instance of Potato(). Try to refactor your code to this
#Test
fun potato() {
val testPotato = Potato()
`when`(fakePotatoProvider.getObservable()).thenReturn(Observable.just(testPotato))
fakePotatoProvider.getObservable().subscribe(potatoConsumer)
verify(potatoConsumer).accept(testPotato)
}
As I mentioned above, the reason why it might be failing is the constant creation of new instances when passing your Potato object, hance that comparison fails.
I'm using coroutines to do an asynchronous call on pull to refresh like so:
class DataFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener {
// other functions here
override fun onRefresh() {
loadDataAsync()
}
private fun loadDataAsync() = async(UI) {
swipeRefreshLayout?.isRefreshing = true
progressLayout?.showContent()
val data = async(CommonPool) {
service?.getData() // suspending function
}.await()
when {
data == null -> showError()
data.isEmpty() -> progressLayout?.showEmpty(null, parentActivity?.getString(R.string.no_data), null)
else -> {
dataAdapter?.updateData(data)
dataAdapter?.notifyDataSetChanged()
progressLayout?.showContent()
}
}
swipeRefreshLayout?.isRefreshing = false
}
}
Everything here works fine when I actually put it on a device. My error, empty, and data states are all handled well and the performance is good. However, I'm also trying to unit test it with Spek. My Spek test looks like this:
#RunWith(JUnitPlatform::class)
class DataFragmentTest : Spek({
describe("The DataFragment") {
var uut: DataFragment? = null
beforeEachTest {
uut = DataFragment()
}
// test other functions
describe("when onRefresh") {
beforeEachTest {
uut?.swipeRefreshLayout = mock()
uut?.onRefresh()
}
it("sets swipeRefreshLayout.isRefreshing to true") {
verify(uut?.swipeRefreshLayout)?.isRefreshing = true // says no interaction with mock
}
}
}
}
The test is failing because it says that there was no interaction with the uut?.swipeRefreshLayout mock. After some experimenting, it seems this is because I'm using the UI context via async(UI). If I make it just be a regular async, I can get the test to pass but then the app crashes because I'm modifying views outside of the UI thread.
Any ideas why this might be occurring? Also, if anyone has any better suggestions for doing this which will make it more testable, I'm all ears.
Thanks.
EDIT: Forgot to mention that I also tried wrapping the verify and the uut?.onRefresh() in a runBlocking, but I still had no success.
If you want to make things clean and consider using MVP architecture in the future you should understand that CourutineContext is external dependency, that should be injected via DI, or passed to your presenter. More details on topic.
The answer for your question is simple, you should use only Unconfined CourutineContext for your tests. (more)
To make things simple create an object e.g. Injection with:
package com.example
object Injection {
val uiContext : CourutineContext = UI
val bgContext : CourutineContext = CommonPool
}
and in test package create absolutely the same object but change to:
package com.example
object Injection {
val uiContext : CourutineContext = Unconfined
val bgContext : CourutineContext = Unconfined
}
and inside your class it will be something like:
val data = async(Injection.bgContext) {service?.getData()}.await()