Home · Android & Kotlin Tutorials

Testing REST APIs Using MockWebServer

In this MockWebserver Android tutorial, you’ll learn how to mock REST API responses and use Espresso UI tests to check how your app handles success or error responses.

5/5 1 Rating

Version

  • Kotlin 1.3, Android 4.4, Android Studio 3.6

MockWebServer is a library from Square — the people who made Retrofit and OkHttp. MockWebServer makes it possible to easily test how your apps behave when making HTTP/HTTPS calls.

A mock web server is a program that mocks the behavior of an actual remote server but doesn’t make calls over the internet. This makes it easy to test different scenarios without internet access and without having to make changes to your remote server.

In this tutorial, you’ll learn:

  • The benefits of using a mock server while testing.
  • How to set up MockWebServer.
  • How to make MockWebServer mock the behavior of your actual server.
  • How to write UI tests to make sure your app functions as expected.

You’ll do this by working on an app called Potterverse that displays a list of characters from the Harry Potter universe and their roles.

Getting Started

Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial.

Inside the materials, you’ll find a starter project and a final project.

The starter project consists of the following files

  1. CharacterAdapter.kt: A RecyclerView adapter that displays the list of characters.
  2. CharacterModel.kt: A Kotlin data model that represents each character.
  3. MainActivity.kt: Displays the list and the error states, when required.
  4. OkHttpProvider.kt: Contains a helper method to access a networking client.
  5. PotterApi.kt: Contains the API key and a method corresponding to the API call you want to make.

In this tutorial, you’ll focus less on the code from the above files and more on setting up MockWebServer and writing tests.

Open Android Studio and select Open an existing Android Studio project.

Open an existing Android Studio project

Select starter-project from the MockWebserver_Materials folder.

Select the starter project from the download materials folder

After you’ve imported the starter project, build and run. You’ll see a screen like this:

starter project initial screen

You’ll notice an error text appears because the API call failed. To fix this, you have to add an API key.

Adding an API Key

For this tutorial, you’ll use the PotterAPI to provide data for your app. You’ll get your API key from https://www.potterapi.com/. You’ll see a GET A KEY button at the top-right edge of the screen.

Get a API key

Clicking on GET A KEY will take you to the sign-up page with a form like the one below:

Sign up for API key

Once you sign up, you’ll see your email ID and API key. Copy the API key.

API Key

Open the PotterApi.kt file and assign the API key as follows:

val API_KEY = YOUR_KEY_HERE

Build and run. Congratulations! The API call no longer fails and the app successfully shows a list of characters from the Harry Potter universe.

MockWebServer working initial starter project

Why Use a Mock Server to Test API Calls

Before you setup your MockWebServer, you might be wondering why you’d want to test your API calls.

There are a few advantages to doing this:

  1. Testing Adapters Is Easy
  2. Sometimes, the API might give responses that aren’t straightforward to serialize and deserialize directly. In other cases, you might want to convert the data from your API response from one data type to another. For example, the API might return dates as Strings but you want to use them as DateTime objects.

    For such cases, you might write an adapter to convert the data. A mock web server makes it easy to test that these adapters are working as expected when making API calls.

  3. Testing Empty and Error States
  4. It can be tricky to test that apps handle empty and error states properly when you’re dealing with a remote server. Servers are designed to be as reliable as possible, making it difficult to get error responses while testing. By using a mock server, you can easily emulate error responses and see if the app handles them properly. All this, without making any changes to your actual server.

Why Use MockWebServer Over Manual Client Mocking

Now that you’re sold on testing your API calls, you may be wondering why you should use MockWebServer when you could instead just mock out whatever class makes your API calls. Here are a few reasons:

  • MockWebServer exercises the full HTTP stack, which would be tedious to do manually.
  • You don’t need to make changes on the app side when using a MockWebServer.
  • It’s easy to recreate edge-cases like throttling and rare error codes.

Configuring MockWebServer

In this section, you’ll configure a MockWebServer instance and make it return the response you saved in the JSON file.

Changing the API URL

The base URL for the app is stored in PotterApp. Open the file to find the following function:

open fun getBaseUrl() = "https://www.potterapi.com"

When you create a test with a mock server, the app shouldn’t use the real URL. Instead, it should use the mock server’s URL. To set this up, you’ll need a mechanism to tell the app to use the real URL normally, but the mock URL when you run tests.

A test runner makes this possible. It helps you configure the conditions you use to run your tests.

First, you need to create an equivalent of PotterApp that returns the mock URL instead of the real URL.

Do this by creating a file named PotterTestApp.kt in the androidTest folder and add the following code to it:

package com.raywenderlich.android.potterverse

class PotterTestApp : PotterApp() {  

  var url = "http://127.0.0.1:8080"  
 
  override fun getBaseUrl(): String {  
    return url  
  }  
}

Here, http://127.0.0.1 is the local URL of your computer and 8080 is the port MockWebServer will use.

After you add the PotterTestApp.kt file, your package structure should look like this:

Package structure

Creating a Test Runner

Now, you need to provide a test runner that uses PotterTestApp instead of PotterApp when running a test.

Create a file named MockTestRunner.kt in the androidTest folder and add the following code to it:

package com.raywenderlich.android.potterverse

import android.app.Application
import android.content.Context
import androidx.test.runner.AndroidJUnitRunner

class MockTestRunner : AndroidJUnitRunner() {

  override fun newApplication(cl: ClassLoader?, className: String?,
                              context: Context?): Application {
    return super.newApplication(cl, PotterTestApp::class.java.name, context)
  }
}

The newApplication method provides the application instance you’ll use in the test. You can see that it returns an application instance using PotterTestApp instead of PotterApp.

Now that you’ve written a test runner, you need to tell Gradle to use it for your project. To do this, open the app-level build.gradle and change the value of testInstrumentationRunner as follows:

testInstrumentationRunner "com.raywenderlich.android.potterverse.MockTestRunner"

Click Sync Now and let Gradle finish syncing. From now on, Gradle will use MockTestRunner whenever you run any UI tests.

To use a mock web server and verify that it’s working as expected, you’ll write a few Espresso tests next.

Using Espresso

Espresso is a testing framework that Android provides to write UI tests. You can use it to emulate user interactions while running tests and to verify if your views respond as expected.

You can test MockWebServer even without Espresso but this tutorial uses it to demonstrate a complete testing scenario.

Note: If you’re new to UI testing using Espresso, check out this introduction to Espresso tutorial.

Before starting to write the test, it’s helpful to review the contents of activity_main.xml. It contain three main views:

  • ProgressBar: Displays the loading status.
  • TextView: Shows the error message.
  • RecyclerView: Display the list of characters.

Create a file named MainActivityTest.kt in the androidTest folder and add this to it:

package com.raywenderlich.android.potterverse

import androidx.test.ext.junit.runners.AndroidJUnit4  
import androidx.test.rule.ActivityTestRule
import org.junit.After  
import org.junit.Before  
import org.junit.Rule  
import org.junit.Test  
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)  
class MainActivityTest {  
 
  @get:Rule  
  val activityRule = ActivityTestRule(MainActivity::class.java, true, false)
 
  @Before  
  fun setup() {  
  }

  @After  
  fun teardown() {
  }
}

This is the basic structure of an Espresso test. You annotate a class with @RunWith(AndroidJUnit4::class) to specify that AndroidJUnit4 is the runner for this test.

A @Before annotation on a method specifies that it will run before every test. Any method with the @After annotation will run after every test. Use these annotations to keep initialization logic common to all tests.

You use ActivityTestRule to specify the Activity that you want to run the UI tests on and the initial actions you want to perform.

Setting up MockWebServer

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

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

Click Sync Now and let Gradle finish syncing.

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

private val mockWebServer = MockWebServer()

Place the cursor on the word MockWebServer and press Alt-Enter, 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 setup some mock responses and write some tests.

Setting up a Mock Response

Before you can actually write your tests, you need to define the response your mock web server will return. To find what the structure of the response of the PotterAPI is, you need to make a real API call first.

Creating a JSON Response

Open a web browser and paste the following URL: https://www.potterapi.com/v1/characters?key=API_KEY. Replace API_KEY with the real API key you obtained in the previous section.

The response will be in the following format:

[  
 {  "_id": "5a0fa4daae5bc100213c232e",  
  "name": "Hannah Abbott",  
  "role": "student",  
  "house": "Hufflepuff",  
  "school": "Hogwarts School of Witchcraft and Wizardry",  
  "__v": 0,  
  "ministryOfMagic": false,  
  "orderOfThePhoenix": false,  
  "dumbledoresArmy": true,  
  "deathEater": false,  
  "bloodStatus": "half-blood",  
  "species": "human"  
  },...
]

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 will look something like this:

Assets directory location

Create a file named success_response.json in the assets directory. This file will contain the response that the mock server will return.

Switch to your browser and copy the first 10 items from the response and paste them in this file. Now, your success_response.json file will look something like this:

[  
  {
    "_id": "5a0fa4daae5bc100213c232e",
    "name": "Hannah Abbott",
    "role": "student",
    "house": "Hufflepuff",
    "school": "Hogwarts School of Witchcraft and Wizardry",
    "__v": 0,
    "ministryOfMagic": false,
    "orderOfThePhoenix": false,
    "dumbledoresArmy": true,
    "deathEater": false,
    "bloodStatus": "half-blood",
    "species": "human"
  },  
  ...
  {
    "_id": "5a0fa842ae5bc100213c2339",
    "name": "Amelia Bones",
    "role": "Head, Department of Magical Law Enforcement",
    "house": "Hufflepuff",
    "school": "Hogwarts School of Witchcraft and Wizardry",
    "__v": 0,
    "ministryOfMagic": true,
    "orderOfThePhoenix": true,
    "dumbledoresArmy": false,
    "deathEater": false,
    "bloodStatus": "unknown",
    "species": "human"
  }
]

Remember to remove the last comma and add a square bracket “]” 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 object.

Start by creating a file named FileReader.kt in androidTest.

Add the following code to FileReader.kt:

package com.raywenderlich.android.potterverse

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 PotterTestApp).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 the mock server is fully set up, you’ll write a few Espresso tests to ensure the server is working as expected. But there’s one last thing you need to do before writing the actual 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 them in the Espresso documentation.

Open the app-level build.gradle and add the following line below the corresponding //TODO 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 do before you can call OkHttp3IdlingResource.

Now, click Sync Now and let Gradle finish syncing.

Open MainActivityTest.kt and add the following lines to setup():

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

Place the cursor on the words IdlingRegistry and OkHttp3IdlingResource respectively, then press Alt-Enter. 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.

For your next step, you’ll actually run a 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 that the method contains a test. MockResponse() creates a mock response that the mock server will return. setResponseCode() specifies the response code.

Since you’re mocking a successful response, the code is 200. setBody() contains the body of the response. In this case, you use the FileReader utility class 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.

Add the following at the end of testSuccessfulResponse():

@Test  
fun testSuccessfulResponse() {  
 ...
 activityRule.launchActivity(null)

 onView(withId(R.id.progress_bar))  
   .check(matches(withEffectiveVisibility(Visibility.GONE)))  
 onView(withId(R.id.character_recyclerview))  
   .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))  
 onView(withId(R.id.textview))  
   .check(matches(withEffectiveVisibility(Visibility.GONE)))
}

The code above launches the Activity and verifies the 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.character_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 then select Run MainActivityTest.

Once the app launches on your device, Android Studio will show you a green tick indicating the successful test.

First test passes

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

Testing a Failed Response

For this test, you’ll create a response that returns after five seconds. You’ve configured the app to request timeout after three seconds, so it will treat a five-second response as a failure.

Add the following method to MainActivityTest.kt:

@Test  
fun testFailedResponse() {  
  mockWebServer.dispatcher = object : Dispatcher() {  
    override fun dispatch(request: RecordedRequest): MockResponse {  
      return MockResponse().throttleBody(1024, 5, TimeUnit.SECONDS)  
    }
  }  
 
  activityRule.launchActivity(null)  
  onView(withId(R.id.progress_bar))  
    .check(matches(withEffectiveVisibility(Visibility.GONE)))  
  onView(withId(R.id.character_recyclerview))  
    .check(matches(withEffectiveVisibility(Visibility.GONE)))  
  onView(withId(R.id.textview))  
    .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))  
  onView(withId(R.id.textview))  
    .check(matches(withText(R.string.something_went_wrong)))
}

In the above code, throttleBody() throttles the response by five seconds. This test verifies one extra thing: It checks if the view with ID R.id.textview contains the text that the string resource R.string.something_went_wrong refers to.

Run the MainActivityTest test. You will see that both the tests pass.

All tests pass

Congratulations! You’ve now set up tests to check both the success and failure responses of your app on a mock web server.

Where to Go From Here?

Download the final project using the Download Materials button at the top or bottom of this tutorial.

In this tutorial, you’ve set up MockWebServer, configured it to mock API calls from a remote server and written UI tests to verify how an app functions.

As a further enhancement, make the app display a text saying Oops! Seems like a server error when the API returns a status code of 500. Configure MockWebServer to return a 500 status code and write a UI test to verify that the app displays the text.

Hopefully, you’ve enjoyed this tutorial! If you have any questions or ideas to share, please join the forum discussion below.

Average Rating

5/5

Add a rating for this content

1 rating

More like this

Contributors

Comments