SkillAgentSearch skills...

Hiroaki

Write idiomatic API integration tests using Kotlin (Unit and Instrumentation)

Install / Use

/learn @JorgeCastilloPrz/Hiroaki
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Hiroaki CircleCI

<img src="https://drive.google.com/uc?id=1dUvJF0sBQCncUdLJ7z6Q7ZVHdD5WKLS0" width="256" height="256" />
Japanese: 'spreading brightness'. Derived from the words 'hiro', which means 'large or wide', and 'aki',
which means 'bright or clear'.

The intention of Hiroaki is to achieve clarity on your API integration tests in an idiomatic way by leveraging the power of Kotlin.

It uses MockWebServer to provide a mock server as a target for your HTTP requests that you'll use to mock your backend.

That enables you to assert over how your program reacts to some predefined server & API behaviors.

Dependency

For Android, add the following dependencies to your build.gradle. Both dependencies are available in Maven Central.

dependencies{
    testImplementation 'me.jorgecastillo:hiroaki-core:0.2.3'
    androidTestImplementation 'me.jorgecastillo:hiroaki-android:0.2.3' // Android instrumentation tests
}

Note that Hiroaki only targets AndroidX. It does not provide support for Android support libraries anymore.

If you do plain Java or Kotlin you'll just need the core artifact on its 0.2.3 version.

Setup

To work with Hiroaki you must extend MockServerSuite on your test class, which takes care of running and shutting down the server for you. If you can't do that, there's also a JUnit4 Rule called MockServerRule with the same goal.

To target the mock server with your requests, you'll need to request the URL from it and pass it to your endpoint creation system / collaborator / entity.

Here you have a plain OkHttp sample.

class GsonNewsNetworkDataSourceTest : MockServerSuite() {

 private lateinit var dataSource: GsonNewsNetworkDataSource

    @Before
    override fun setup() {
        super.setup()
        val mockServerUrl = server.url("/v2/news")
        dataSource = NewsDataSource(mockServerUrl)
    }

    /*...add tests here!...*/
}

/*Some random data source, probably on a different file*/
class NewsDataSource(var baseUrl: HttpUrl) {

  fun getNews(): String? {
      val client = OkHttpClient()
      val request = Request.Builder()
              .url(baseUrl)
              .build()

      val response = client.newCall(request).execute()
      return response.body()?.string()
  }
}

If you have an endpoint factory, or even a DI system providing injected endpoints, you'll need to have a good design on your app to pass the mock server url to it. That's on you and is different for every project.

Syntax for Retrofit

However, Hiroaki provides syntax for waking up mock Retrofit services in case you need one for writing some unit tests for your api client / data source as the subject under test.

class GsonNewsNetworkDataSourceTest : MockServerSuite() {

    private lateinit var dataSource: GsonNewsNetworkDataSource

    @Before
    override fun setup() {
        super.setup()
        // Use server.retrofitService() to build the service targeting the mock URL
        dataSource = GsonNewsNetworkDataSource(server.retrofitService(
                GsonNewsApiService::class.java,
                GsonConverterFactory.create()))
    }

    /*...*/
}

This will use a default OkHttpClient instance created for you with basic configuration. For more detailed configuration, retrofitService() function offers an optional parameter to pass a custom OkHttpClient:

val customClient = OkHttpClient.Builder()
        .connectTimeout(2, TimeUnit.SECONDS)
        .readTimeout(2, TimeUnit.SECONDS)
        .writeTimeout(2, TimeUnit.SECONDS)
        .build()

dataSource = GsonNewsNetworkDataSource(server.retrofitService(
                GsonNewsApiService::class.java,
                GsonConverterFactory.create(),
                okHttpClient = customClient))

JUnit4 Rule

As mentioned before, here you have the alternative JUnit4 rule to avoid using extension if that's your need:

@RunWith(MockitoJUnitRunner::class)
class RuleNetworkDataSourceTest {

    private lateinit var dataSource: JacksonNewsNetworkDataSource
    @get:Rule val rule: MockServerRule = MockServerRule()

    @Before
    fun setup() {
        dataSource = JacksonNewsNetworkDataSource(rule.server.retrofitService(
                JacksonNewsApiService::class.java,
                JacksonConverterFactory.create()))
    }

    @Test
    fun sendsGetNews() {
       // you'll need to call the server through the rule
       rule.server.whenever(GET, "v2/top-headlines")
                  .thenRespond(success(jsonBody = fileBody("GetNews.json")))
                  // Can also inline a body or use the json DSL

       runBlocking { dataSource.getNews() }

       /*...*/
    }
}

Mocking Responses

With Hiroaki, you can mock request responses as if it was mockito:

@Test
fun chainResponses() {
    server.whenever(Method.GET, "v2/top-headlines")
            .thenRespond(success(jsonBody = fileBody("GetNews.json")))
            // Can also inline a body or use the json DSL

    val news = runBlocking { dataSource.getNews() }

    /*...*/
}

This ensures that whenever the endpoint v2/top-headlines is called with the given conditions the server will respond with the mocked response we're providing.

These are all the supported params for whenever that you can match to. All of them are optional except sentToPath:

server.whenever(method = Method.GET,
                sentToPath = "v2/top-headlines",
                queryParams = params("sources" to "crypto-coins-news",
                        "apiKey" to "21a12ef352b649caa97499bed2e77350"),
                jsonBody = fileBody("GetNews.json"), // (file, inline, or JsonDSL)
                headers = headers("Cache-Control" to "max-age=640000"))
      .thenRespond(success(jsonFileName = "GetNews.json"))

Also note in the previous snippets the success() function when mocking the response. function success() is a shortcut to provide a mocked successful response. You can also use error() and response(). All of them are mocking functions that allow you to pass the following optional arguments:

  • code Int return http status code for the mocked response.
  • jsonBody JsonBody, JsonFileBody, Json or JsonArray: json for your mocked response body.
  • headers Is a Map<String,String> headers to attach to the mocked response.

If you don't want to use the succes(), error() or response() shortcut functions, you can still pass your own custom MockResponse.

Chaining Mocked Responses

You can also chain a bunch of mocked responses:

server.whenever(Method.GET, "v2/top-headlines")
                .thenRespond(success(jsonBody = fileBody("GetNews.json")))
                .thenRespond(success(jsonBody = fileBody("GetSingleNew.json")))
                .thenRespond(success(jsonBody = fileBody("GetNews.json")))

Each time the endpoint is called under the given conditions, the server will return the next mocked response from the list, following the same order.

Dynamic dispatch

Sometimes you want a response to depend on the request sent. For that reason, Hiroaki provides the thenDispatch method:

server.whenever(Method.GET, "v2/top-headlines")
      .thenDispatch { request -> success(jsonBody = inlineBody("{\"requestPath\" : ${request.path}}")) }

You can combine as many thenRespond() and thenDispatch() calls as you want.

server.whenever(Method.GET, "v2/top-headlines")
      .thenRespond(success())
      .thenDispatch { request -> success(jsonBody = inlineBody("{\"requestPath\" : ${request.path}}")) }
      .thenRespond(error())

Delay Responses

Mimic server response delays with delay(), an extension function for MockResponse to pass a delay in millis: response.delay(millis):

server.whenever(Method.GET, "v2/top-headlines")
      .thenRespond(success(jsonBody = fileBody("GetNews.json")).delay(250))
      .thenRespond(success(jsonBody = fileBody("GetSingleNew.json")).delay(500))
      .thenRespond(success(jsonBody = fileBody("GetNews.json")).delay(1000))

// Also for dispatched responses
server.whenever(Method.GET, "v2/top-headlines")
      .thenDispatch { request -> success().delay(250) }

Throttle response bodies

Sometimes you want to emulate bad network conditions, so you can throttle your response body like:

server.whenever(GET, "v2/top-headlines").thenRespond(error().throttle(64, 1000))

Here, you are asking the server to throttle and write chunks of 64 bytes per second (1000 millis).

Verifying Requests

Hiroaki provides a highly configurable verify() function to perform verification over executed HTTP requests. Its arguments are optional so you're free to configure the assertion in a way that matches your needs.

@Test
fun verifiesCall() {
    server.whenever(Method.GET, "v2/top-headlines")
            .thenRespond(success(jsonBody = fileBody("GetNews.json")))
            .thenRespond(success(jsonBody = fileBody("GetSingleNew.json")))
            .thenRespond(success(jsonBody = fileBody("GetNews.json")))

    runBlocking {
        dataSource.getNews()
        dataSource.getSingleNew()
        dataSource.getNews()
    }

    server.verify("v2/top-headlines").called(
            times = times(2),
            order = order(1, 3),
            method = Method.POST,
            headers = headers("Cache-Control" to "max-age=640000"),
            queryParams = params(
                                "sources" to "cr
View on GitHub
GitHub Stars369
CategoryDevelopment
Updated1mo ago
Forks12

Languages

Kotlin

Security Score

95/100

Audited on Mar 1, 2026

No findings