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
.
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.5.0"
//mutliplatform and lighter weight option for ktor only library users
testImplementation "io.github.denisbronx.netmock:netmock-engine:0.5.0"
//library for accessing local json files in the test folder
testImplementation "io.github.denisbronx.netmock:netmock-resources:0.5.0"
}
@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")
}
)
//...
}
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)
//...
}
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
}
)
//...
}
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)
//...
}
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 the NetMockRequest:NetMockResponse
you previously mocked.
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.
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.
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 |