Unit test fail with Error(MockKException) for MockResponse() - android

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")

Related

Android Mockito assertEquals with mocked object

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.

how to throw an exception on a retrofit call

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

How I can mock response of Apollo Android

I'm trying to mock response to my query but I can't because the builder needed and doesn't know how to pass operations?
Basicly I have network class like this :
class NetworkService #Inject constructor(
private val apolloClient: ApolloClient
) {
suspend fun <D : Operation.Data, T, V : Operation.Variables> suspendedQuery(
query: Query<D, T, V>,
cachePolicy: HttpCachePolicy.Policy = HttpCachePolicy.NETWORK_FIRST
): Resource<Response<T>> {
val response = try {
apolloClient.query(query)
.toBuilder().httpCachePolicy(cachePolicy)
.build()
.await()
} catch (e: ApolloException) {
return Resource.error(e.localizedMessage)
}
return if (response.hasErrors()) {
Resource.error(response.errors.toString())
} else {
Resource.success(response)
}
}
}
and I want to mock the response returned from this function like this
I successed when I'm returning an error
val expectedResponse = Resource.error<Response<MyQuery.Data>>("ERROR")
But I have problem to mocking the resposne here:
val expectedResponse = Resource.success<Response<MyQuery.Data>>(Response("Response.builder(Operation<>)"))
I want to know How to build the inside the quote "Response.builder(Operation<>)"?
It would appear that this is a long running problem according to the apollo-android issues list. I can see that you've asked a similar question there also.
I found this issue which looks to be resolving this problem in the not too distant future. It was supposed to be this month but now looks like next.
So think this means that you're only solution right now is to use mock web server, and have it return a sample response to the client. This is how we have implemented our tests currently.

Suspending function test with MockWebServer

I'm testing api that returns result using suspending function with MockWebServer, but it does not work with runBlockingTest, testCoroutineDispatcher, testCorounieScope unless a launch builder is used, why?
abstract class AbstractPostApiTest {
internal lateinit var mockWebServer: MockWebServer
private val responseAsString by lazy {
getResourceAsText(RESPONSE_JSON_PATH)
}
#BeforeEach
open fun setUp() {
mockWebServer = MockWebServer()
println("AbstractPostApiTest setUp() $mockWebServer")
}
#AfterEach
open fun tearDown() {
mockWebServer.shutdown()
}
companion object {
const val RESPONSE_JSON_PATH = "posts.json"
}
#Throws(IOException::class)
fun enqueueResponse(
code: Int = 200,
headers: Map<String, String>? = null
): MockResponse {
// Define mock response
val mockResponse = MockResponse()
// Set response code
mockResponse.setResponseCode(code)
// Set headers
headers?.let {
for ((key, value) in it) {
mockResponse.addHeader(key, value)
}
}
// Set body
mockWebServer.enqueue(
mockResponse.setBody(responseAsString)
)
return mockResponse
}
}
class PostApiTest : AbstractPostApiTest() {
private lateinit var postApi: PostApiCoroutines
private val testCoroutineDispatcher = TestCoroutineDispatcher()
private val testCoroutineScope = TestCoroutineScope(testCoroutineDispatcher)
#BeforeEach
override fun setUp() {
super.setUp()
val okHttpClient = OkHttpClient
.Builder()
.build()
postApi = Retrofit.Builder()
.baseUrl(mockWebServer.url("/"))
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build()
.create(PostApiCoroutines::class.java)
Dispatchers.setMain(testCoroutineDispatcher)
}
#AfterEach
override fun tearDown() {
super.tearDown()
Dispatchers.resetMain()
try {
testCoroutineScope.cleanupTestCoroutines()
} catch (exception: Exception) {
exception.printStackTrace()
}
}
#Test
fun `Given we have a valid request, should be done to correct url`() =
testCoroutineScope.runBlockingTest {
// GIVEN
enqueueResponse(200, RESPONSE_JSON_PATH)
// WHEN
postApi.getPostsResponse()
advanceUntilIdle()
val request = mockWebServer.takeRequest()
// THEN
Truth.assertThat(request.path).isEqualTo("/posts")
}
}
Results error: java.lang.IllegalStateException: This job has not completed yet
This test does not work if launch builder is used, and if launch builder is used it does not require testCoroutineDispatcher or testCoroutineScope, what's the reason for this? Normally suspending functions pass without being in another scope even with runBlockingTest
#Test
fun `Given we have a valid request, should be done to correct url`() =
runBlockingTest {
// GIVEN
enqueueResponse(200, RESPONSE_JSON_PATH)
// WHEN
launch {
postApi.getPosts()
}
val request = mockWebServer.takeRequest()
// THEN
Truth.assertThat(request.path).isEqualTo("/posts")
}
The one above works.
Also the test below pass some of the time.
#Test
fun Given api return 200, should have list of posts() =
testCoroutineScope.runBlockingTest {
// GIVEN
enqueueResponse(200)
// WHEN
var posts: List<Post> = emptyList()
launch {
posts = postApi.getPosts()
}
advanceUntilIdle()
// THEN
Truth.assertThat(posts).isNotNull()
Truth.assertThat(posts.size).isEqualTo(100)
}
I tried many combinations invoking posts = postApi.getPosts() without launch, using async, putting enqueueResponse(200) inside async async { enqueueResponse(200) }.await() but tests failed, sometimes it pass sometimes it does not some with each combination.
There is a bug with runBlockTest not waiting for other threads/jobs to complete before completing the coroutine that the test is running in.
I tried using runBlocking with success (I use the awesome port of Hamcrest to Kotlin Hamkrest)
fun `run test` = runBlocking {
mockWebServer.enqueue(MockResponse().setResponseCode(200).setBody(""))
// make HTTP call
val result = mockWebServer.takeRequest(2000L, TimeUnit.MILLISECONDS)
assertThat(result != null, equalTo(true))
}
There's a few things to note here:
The use of thread blocking calls should never be called without a timeout. Always better to fail with nothing, then to block a thread forever.
The use of runBlocking might be considered by some to be no no. However this blog post outlines the different method of running concurrent code, and the different use cases for them. We normally want to use runBlockingTest or (TestCoroutineDispatcher.runBlockingTest) so that our test code and app code are synchronised. By using the same Dispatcher we can make sure that the jobs all finish, etc. TestCoroutineDispatcher also has that handy "clock" feature to make delays disappear. However when testing the HTTP layer of the application, and where there is a mock server running on a separate thread we have a synchronisation point being takeRequest. So we can happily use runBlocking to allow us to use coroutines and a mock server running on a different thread work together with no problems.

RxJava repeatWhen doesn't called again in Mockito

I am writing a testing code using Mockito.
I am using RxJava and Retrofit to get the resource from the server.
mockRestService.get(id) method returns Observable.
First, the request call returns an item with the status of "not ready".
So I should use the Rx operator "repeatWhen()".
After some retry, the server sends an item with the status of "complete".
Below is my Presenter code.
val getItem = restService.getItem(id)
.repeatWhen { it.delay(1000, TimeUnit.MILLISECONDS) }
.takeUntil { it.status == "complete" }
And below is my testing code.
To mimic server behaviour, I wrote below the testing code.
#Test
fun printJobTest_one_job_normal_case() {
val notReadyItem = Item(status = "not ready")
val completeItem = Item(status = "complete")
Mockito.`when`(mockRestService.getItem(id))
.thenReturn(Observable.just(notReadyItem)) // First 2 response is "not ready" status
.thenReturn(Observable.just(notReadyItem))
.thenReturn(Observable.just(completeItem)) // Third response is "complete" status
// verify
}
To mimic the server behaviour, I used chained "thenReturn()" method.
But only always the item which's status is "not ready" occurs.
I... found the solution.
It's easy...
Just below code works fine.
#Test
fun printJobTest_one_job_normal_case() {
val notReadyItem = Item(status = "not ready")
val completeItem = Item(status = "complete")
Mockito.`when`(mockRestService.getItem(id))
.thenReturn(Observable.just(notReadyItem, notReadyItem, completeItem))
// verify
}
I just removed the chained method "thenReturn", and moved the variables to the parameter of Observable.

Categories

Resources