NetMock
NetMock copied to clipboard
NetMock
NetMock is a powerful testing library that makes it incredibly easy to unit test your network
requests.
It is compatible with Java, Kotlin, Android, and Kotlin-Multiplatform making it a versatile
option for developers working on different platforms.
The library offers a variety of features that can help you test your network requests against any
network library, including OkHttp, Ktor, and Retrofit.
You can use a mock-like API to test your requests, making the test code much simpler and more
readable.
NetMock comes in two different flavors: netmock-server and netmock-engine.
The netmock-server flavor is compatible with Java, Kotlin, and Android, and it is library
independent as it allows you to mock network requests by redirecting requests to
a localhost web server
using MockWebServer.
This flavor is perfect for developers that work on non-Multiplatform projects and that want to test
their network requests without having to worry about setting up a separate server.
The netmock-engine flavor, on the other hand, is designed specifically for developers using Ktor
or working with Kotlin Multiplatform.
It allows you to use MockEngine instead of a localhost server, making it a more lightweight and
multiplatform option for those working with Ktor.
If your project is not a Kotlin multiplatform project, and you are using a variety of libraries, and
you don't want to import both flavors, just use netmock-server as it is compatible with all the
libraries including Ktor.
Install
Netmock is available on Maven Central.
For Gradle users, add the following to your module’s build.gradle
dependencies {
//compatible with all libraries
testImplementation "io.github.denisbronx.netmock:netmock-server:0.7.0"
//mutliplatform and lighter weight option for ktor only library users
testImplementation "io.github.denisbronx.netmock:netmock-engine:0.7.0"
//library for accessing local json files in the test folder
testImplementation "io.github.denisbronx.netmock:netmock-resources:0.7.0"
}
Examples
Initialization
netmock-server initialization
netmock-engine initialization
Mock requests and responses
@Test
fun `my test`() {
netMock.addMock(
request = {
//exact method
method = Method.Post
//exact request url: scheme, base url, path, params...
requestUrl = "https://google.com/somePath?paramKey1=paramValue1"
//must-have headers, as some clients add extra headers you may not want to check them all
//if you are using ktor and your response body is a json, you must have "Content-Type: application/json" as header
mandatoryHeaders = mapOf("a" to "b", "b" to "c")
//request body, must be a String (this allows you to test your parsing)
body = """{"id": "2"}"""
//or, you can read a file in "test/resources"
//body = readFromResources("requests/request_body.json")
},
response = {
//status code
code = 200
//must-have headers
//if you are using ktor and your response body is a json, you must have "Content-Type: application/json" as header
mandatoryHeaders = mapOf("a" to "b", "b" to "c")
//response body, must be a String (this allows you to test your parsing)
body = """{"data": "text"}"""
//or, you can read a file in "test/resources"
//body = readFromResources("responses/response_body.json")
}
)
//...
}
Reuse requests and responses
private val request = NetMockRequest(
method = Method.Post,
requestUrl = "https://google.com/somePath?paramKey1=paramValue1",
mandatoryHeaders = mapOf("a" to "b", "b" to "c"),
body = readFromResources("requests/request_body.json")
)
private val response = NetMockResponse(
code = 200,
mandatoryHeaders = mapOf("a" to "b", "b" to "c"),
body = readFromResources("responses/response_body.json")
)
@Test
fun `my test`() {
netMock.addMock(request, response)
//...
}
Templates
private val templateRequest = NetMockRequest(
method = Method.Post,
requestUrl = "https://google.com/somePath?paramKey1=paramValue1",
mandatoryHeaders = mapOf("a" to "b", "b" to "c"),
body = readFromResources("requests/request_body.json")
)
private val templateResponse = NetMockResponse(
code = 200,
mandatoryHeaders = mapOf("a" to "b", "b" to "c"),
body = readFromResources("responses/response_body.json")
)
@Test
fun `my test`() {
netMock.addMock(
request = {
fromRequest(templateRequest)
method = Method.Put
},
response = {
fromResponse(templateResponse)
code = 201
}
)
//...
}
Custom Request Matchers
If you want to use more relaxed matching criteria you can add your own request matcher by using
addMockWithCustomMatcher instead of addMock:
private val response = NetMockResponse(
code = 200,
mandatoryHeaders = mapOf("a" to "b", "b" to "c"),
body = readFromResources("responses/response_body.json")
)
@Test
fun `my test`() {
netMock.addMockWithCustomMatcher(
requestMatcher = { interceptedRequest ->
interceptedRequest.requestUrl.contains("https://google.com/somePath") && method == Method.Post
},
response = response
)
//...
}
Mock the same request multiple times
Each mock intercepts only 1 request, once a request is intercepted NetMock will remove the mock
from the queue.
This allows you to test what your code exactly does.
If your code is making the same request multiple times (i.e polling) you would need to add a mock
for each expected request:
@Test
fun `my test`() {
netMock.addMock(request, response)
netMock.addMock(request, response)
netMock.addMock(request, response)
//...
}
Verify intercepted requests
If you want to verify that a request has been intercepted you can use netMock.interceptedRequests,
which will return a list of all the interceptedRequests by NetMock:
@Test
fun `my test`() {
netMock.addMock(request, response)
//...
assertEquals(listOf(request), netMock.interceptedRequests)
}
You can also check the requests that have not been intercepted yet with:
netMock.allowedMocks
This will return a NetMockRequestResponse which is the pair of theNetMockRequest:NetMockResponse
you previously mocked.
Not mocked requests
By default, requests that are not mocked will produce a 400 Bad Request response and will be
logged as errors in the console.
You can override this behaviour by setting a default response:
netMock.defaultResponse = NetMockResponse(
code = 200,
mandatoryHeaders = mapOf("a" to "b", "b" to "c"),
body = readFromResources("responses/response_body.json")
)
by doing so, all the not mocked requests will return the specified response and no logs will be printed in the console.
Resources
When working with request and response bodies, it may not be ideal to create string constants in
your tests (i.e. long JSONs that compromise tests readability, sharing bodies between test
classes...).
You can instead read from a local file in your test resources folder:
//Reads the text from the module's "src/test/resources/responses/products_response_body.json" file
val responseBody = readFromResources("responses/products_response_body.json")
By doing so, the IDE will properly format the file for you and you can even copy/paste HTTP request and response bodies from your real web server to create representative test cases.
Multiplatform Resources
If you are working on a multiplatform project, your resources folder will be located in a
different path.
Use the following methods for reading the correct files:
| Method | Resolved path |
|---|---|
readFromCommonResources |
src/commonTest/resources |
readFromJvmResources |
src/jvmTest/resources |
readFromNativeResources |
src/nativeTest/resources |