Mock Responses with OkHttp & Retrofit
This blog post does not explore Retrofit nor OkHttp and all their glory. Instead, we are looking into OkHttp’s testing API and how to use that to provide confidence into our codebase.
Testing is often daunting but crucial for software development. I am going to keep this post short and sweet. We will explore together the OkHttp‘s “MockWebServer” and how to integrate with Retrofit.
Target Audience: Retrofit Users
The Why?
We need our tests non-flaky, and reliable! What that means is:
- Our tests should run in isolation
- Our tests should not be affected by external factors
- Our tests conditions are controllable
A nice explanation of a flaky test from the “Flaky Tests - A War that Never Ends”
A flaky test is a test which could fail or pass for the same configuration
All the above points are needed so that we can control and test different scenarios. For example, if the tests are making a real network call, then they could fail when the network connectivity is lost.
Setup 🔨
You need to add the following dependency in build.gradle
.
testImplementation("com.squareup.okhttp3:mockwebserver:see.latest.version")
Explore more about MockWebServer dependency at the Github Repository.
Explore 🛫
The MockWebServer is really powerful and provides us with some incredible APIs to ease the pain when testing such features.
Some of the highlights are:
- Mocking Responses
- Throttling for bodies
- Throttling for headers
- Many many more
From the Readme of MockWebServer:
This library makes it easy to test that your app Does The Right Thing when it makes HTTP and HTTPS calls. It lets you specify which responses to return and then verify that requests were made as expected.
Create the mock web server 🏗
val mockWebServer = MockWebServer()
When you are ready to start your tests, make sure you “Start” the mock server like:
mockWebServer.start()
Mocking a response
MockResponse()
.setResponseCode(HttpURLConnection.HTTP_OK)
.setBody(""{\"status\":\"error\",\"code\":\"responseCode\"}"
")
To instruct the server to return the mocked response, you need to enqueue the mock response such as:
mockWebServer.enqueue(mockResponse)
For further studying, head to MockWebServer’s Javadoc here.
Basic Setup for unit tests
The following setup will make sure that you start your mock server and shut it down between tests.
class YourTest {
lateinit var mockWebServer: MockWebServer
@Before
fun setUp() {
mockWebServer = MockWebServer()
mockWebServer.start()
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
}
MockWebServer with Retrofit
Now, let’s explore, how to use Retrofit with all the above.
Basic setup for Retrofit looks like:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://ourapi.com/")
.build()
We are used to passing a String to the method .baseUrl(string)
. However, there is an overload which takes an HttpUrl object. The HttpUrl object is what the mock web server exposes and we could use to integrate it into tests.
If we actually check the source code the .baseUrl(string)
internally looks like:
public Builder baseUrl(String baseUrl) {
checkNotNull(baseUrl, "baseUrl == null");
return baseUrl(HttpUrl.get(baseUrl));
}
What we then pass for the base URL:
- Production:
HttpUrl.get("https://ourapi.com/")
- Tests:
mockWebServer.url("/")
The Transformation
First, pass the above to your Retrofit builder and then start receiving the mock responses and leveraging the test environment for your benefit.
As you can see from the examples below, we should now inject the HttpUrl to allow the MockWebServer to do its magic.
Before
class OurApi(
private val baseUrl: String = "https://ourapi.com/"
) {
private val retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(MoshiConverterFactory.create())
.build()
}
After
class OurApi(
private val baseUrl: HttpUrl = HttpUrl.get("https://ourapi.com/")
) {
private val retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(MoshiConverterFactory.create())
.build()
}
That’s it really, thanks for reading the post!
Happy coding and testing!
Feel free to ping me on twitter.
Till next time! 👋
(👇 check out the bonus section below 👇)
Bonus
You can place different responses in your Test Resources which you can instruct the MockWebServer instance to return.
Steps:
- Create a folder under the following path:
~/${MODULE}/src/test/resources
- Place there the responses as JSON files such as:
error.json
- From your unit test read the file as a String and pass to the mock web server instance
Read json files as Strings
object FileUtils {
fun readTestResourceFile(fileName: String): String {
val fileInputStream = javaClass.classLoader?.getResourceAsStream(fileName)
return fileInputStream?.bufferedReader()?.readText() ?: ""
}
}
// pass to mock web server
val response = MockResponse()
response.setResponseCode(HttpURLConnection.HTTP_OK)
response.setBody(FileUtils.readTestResourceFile("error.json"))