Testing REST APIs Using MockWebServer

Learn how to mock a REST API with MockWebServer and easily test your business logic using Espresso to check how your UI handle success or error responses. By Subhrajyoti Sen.

3.5 (2) · 2 Reviews

Download materials
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Setting up MockWebServer

You’ll need to add the Gradle dependency for MockWebServer first to use instances of MockWebServer. Open the app-level build.gradle and add the following line below the corresponding // TODO 2 at the end of the file:

androidTestImplementation "com.squareup.okhttp3:mockwebserver:4.9.3"

Click Sync Now and let Gradle finish syncing.

Next, you’ll create an instance of MockWebServer. Add the following line before setup() in MainActivityTest.kt:

private val mockWebServer = MockWebServer()

Place the cursor on the word MockWebServer and press Alt-Enter on Windows or Option-Return on Mac. Then, choose Import to automatically add the missing import line and fix the build.

You have to start the server before every test and stop it after every test. You can do this by modifying setup() as follows:

@Before  
fun setup() {  
  mockWebServer.start(8080)
}

This starts the mock server on the 8080 port.

Next, modify teardown() to stop the server:

@After  
fun teardown() {  
  mockWebServer.shutdown()  
}

And that’s all it takes to get MockWebServer ready. All you need to do now is to set up some mock responses and write some tests.

Setting up a Mock Response

Before writing your tests, you must define the response your mock web server will return. To find the structure of the Imgflip API’s response, you need to make a real API call first.

Creating a JSON Response

Open a web browser and paste the following URL: https://api.imgflip.com/get_memes.

The response will be in the following format:

{
  "success": true,
  "data": {
    "memes": [
      {
        "id": "181913649",
        "name": "Drake Hotline Bling",
        "url": "https://i.imgflip.com/30b1gx.jpg",
        "width": 1200,
        "height": 1200,
        "box_count": 2
      },...
    ]
  }
}

Keep the browser open; you’ll need it later.

If it doesn’t already exist, create a directory named assets in your app module. It’ll look something like this:

Assets directory location

Create a file in the assets directory and name it success_response.json. This file will contain the response the mock server will return.

Switch to your browser, copy the first eight items from the response and paste them into this file. Now, your success_response.json file looks something like this:

{
  "success": true,
  "data": {
    "memes": [
      {
        "id": "181913649",
        "name": "Drake Hotline Bling",
        "url": "https://i.imgflip.com/30b1gx.jpg",
        "width": 1200,
        "height": 1200,
        "box_count": 2
      },
      {
        "id": "87743020",
        "name": "Two Buttons",
        "url": "https://i.imgflip.com/1g8my4.jpg",
        "width": 600,
        "height": 908,
        "box_count": 3
      },
      {
        "id": "112126428",
        "name": "Distracted Boyfriend",
        "url": "https://i.imgflip.com/1ur9b0.jpg",
        "width": 1200,
        "height": 800,
        "box_count": 3
      },
      {
        "id": "131087935",
        "name": "Running Away Balloon",
        "url": "https://i.imgflip.com/261o3j.jpg",
        "width": 761,
        "height": 1024,
        "box_count": 5
      },
      {
        "id": "247375501",
        "name": "Buff Doge vs. Cheems",
        "url": "https://i.imgflip.com/43a45p.png",
        "width": 937,
        "height": 720,
        "box_count": 4
      },
      {
        "id": "124822590",
        "name": "Left Exit 12 Off Ramp",
        "url": "https://i.imgflip.com/22bdq6.jpg",
        "width": 804,
        "height": 767,
        "box_count": 3
      },
      {
        "id": "129242436",
        "name": "Change My Mind",
        "url": "https://i.imgflip.com/24y43o.jpg",
        "width": 482,
        "height": 361,
        "box_count": 2
      },
      {
        "id": "217743513",
        "name": "UNO Draw 25 Cards",
        "url": "https://i.imgflip.com/3lmzyx.jpg",
        "width": 500,
        "height": 494,
        "box_count": 2
      }
    ]
  }
}

Remember to remove the last comma and add the brackets “]}}” at the end of the file to make sure it’s a valid JSON file.

Creating a File Reader to Read the Response

MockWebServer can’t read the response from the JSON file directly. For this, you need to create a file reader to read the contents of the JSON file and convert them into a String.

Start by creating a file in androidTest and naming it FileReader.kt.

Add the following code to FileReader.kt:

package com.company.android.whatsthememe

import androidx.test.platform.app.InstrumentationRegistry  
import java.io.IOException  
import java.io.InputStreamReader  
 
object FileReader {  
  fun readStringFromFile(fileName: String): String {  
    try {  
      val inputStream = (InstrumentationRegistry.getInstrumentation().targetContext  
        .applicationContext as MemeTestApp).assets.open(fileName)  
      val builder = StringBuilder()  
      val reader = InputStreamReader(inputStream, "UTF-8")  
      reader.readLines().forEach {  
        builder.append(it)  
      }  
      return builder.toString()  
    } catch (e: IOException) {  
      throw e  
    }  
  }
}

FileReader consists of a single method: readStringFromFile(). This opens the JSON file as an InputStream and writes its contents in a String line by line. The final, built-up String is then returned. You’ll use FileReader to read from the success_response.json file you created earlier.

Writing the Tests

Now that you’ve fully set up the mock server, you’ll write a few Espresso tests to ensure the server is working as expected. But you must do one last thing before writing the tests.

API calls are asynchronous tasks, so you need a way to tell Espresso to wait for the API calls to complete. You’ll use an idling resource to solve this.

Setting up an Idling Resource

An idling resource represents an asynchronous operation whose results affect subsequent operations in a UI test. You can read more about idling resources in the Espresso documentation.

Open the app-level build.gradle and add the following line below the corresponding // TODO 3 at the end of the file:

androidTestImplementation 'com.jakewharton.espresso:okhttp3-idling-resource:1.0.0'

This adds the Gradle dependency, which you need to have before you can call OkHttp3IdlingResource.

Now, click Sync Now and let Gradle finish syncing.

Open MainActivityTest.kt and add a member variable for OkHttp3IdlingResource just below the mockWebServer one, as follows:

private lateinit var okHttp3IdlingResource: OkHttp3IdlingResource

Next, add the following lines to the beginning of setup() to set up and register the idling resource just before starting the mockWebServer:

@Before  
fun setup() {
  okHttp3IdlingResource = OkHttp3IdlingResource.create(
    "okhttp",
     OkHttpProvider.getOkHttpClient()
  )
  IdlingRegistry.getInstance().register(
    okHttp3IdlingResource
  )
  mockWebServer.start(8080)  
}

Place the cursor on the words IdlingRegistry and OkHttp3IdlingResource, respectively, then press Alt-Enter or Option-Return. Next, choose Import to automatically add the missing import lines and fix the build.

OkHttp3IdlingResource is a class that the okhttp-idling-resource library provides. It tells Espresso to wait for the OkHttp client to finish performing its task. OkHttp is the networking client used in the project.

OkHttpProvider is a helper class in the project that gives you access to a static OkHttp instance.

Every time you register an idling resource, you also have to unregister it. Add the following code at the end of teardown(), just after shutting down the mockWebServer:

IdlingRegistry.getInstance().unregister(okHttp3IdlingResource)

For your next step, you’ll test to see what happens when the case is successful.

Testing a Successful Case

For your first test case, you’ll make MockWebServer return the response from the JSON file. You can tell MockWebServer what to send as a response by creating a dispatcher.

Add the following method to the MainActivityTest.kt class and import dependencies:

@Test  
fun testSuccessfulResponse() {  
  mockWebServer.dispatcher = object : Dispatcher() {  
    override fun dispatch(request: RecordedRequest): MockResponse {  
      return MockResponse()  
          .setResponseCode(200)  
      .setBody(FileReader.readStringFromFile("success_response.json"))  
    }
  }  
}

Here’s what’s happening with this code:

@Test tells Espresso the method contains a test. MockResponse() creates a mock response the mock server will return. setResponseCode() specifies the response code.

Because you’re mocking a successful response, the code is 200. setBody() contains the body of the response. In this case, you use FileReader to convert the JSON response into a String instance.

With the mock server set up, you need to write the UI test to run to verify the successful response.

In this case, the MockWebServer returns the response almost immediately, and you might not need an idling resource for the test to work. But it’s recommended to use an idling resource when a test depends on an asynchronous operation. Ensuring the test conditions are checked only after the asynchronous operation is completed reduces test flakiness.

Add the following at the end of testSuccessfulResponse() and import dependencies:

val scenario = launchActivity<MainActivity>()

onView(withId(R.id.progress_bar))
 .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)))
onView(withId(R.id.meme_recyclerview))
 .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
onView(withId(R.id.textview))
  .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)))

scenario.close()

The code above launches the Activity and verifies the that following conditions are met after a successful response:

  • The view with R.id.progress_bar has a visibility of View.GONE.
  • The one with R.id.meme_recyclerview has a visibility of View.VISIBLE.
  • The view with R.id.textview has a visibility of View.GONE.

Run the test by clicking the Play icon next to the class definition and then selecting Run MainActivityTest.

Once the app launches on your device, Android Studio shows you a green check mark indicating the successful test.

First test passes

Now, it’s time to see what happens when you get a failed response.