/mock-fuel

JUnit 5 extension to easily test with the http client Fuel for Kotlin

Primary LanguageKotlinMIT LicenseMIT



Testing utility for the Kotlin HTTP client Fuel powered by JUnit 5

Use case

When you want to unit- or integration-test with external dependencies you often have to mock the external HTTP requests. In the same step you might also want to ensure the request to the external service is correct formatted and structured.

This is where mock-fuel comes into place. Without having to spin-up a complete local web-server within your tests, you can validate your requests and mock external calls.

Benefits over alternatives

As the name suggests mock-fuel is made for that one HTTP client Fuel. When you use something else, this is not the best solution for you. But if you use Fuel and look for alternatives there are:

  • Mocking each response with a common mock framework
    • Can be frustrating because of Fuel's API
  • Using something like mock-server or okhttp mockwebserver
    • Always spins-up a local web-server which takes time and increases your test runtime a lot

Because mock-fuel directly overrides how Fuel is sending HTTP requests it is significant faster than starting a local webserver for every test.

Downsides

The biggest benefit is also a pitfall. Be aware that mock-fuel is overriding the default Client of Fuel. That might lead to different exception handling and when you also use your own Client implementation for Fuel mock-fuel will not work for you.

Requirements

  • Fuel >2.0.0
  • JUnit >5.0
  • Kotlin >1.3.0

Usage

Setup your test dependencies to include mock-fuel. Make sure that you also provide an own version of Fuel.

Maven

Replace VERSION with above latest release version.

<dependencies>
    <dependency>
        <groupId>com.github.KennethWussmann</groupId>
        <artifactId>mock-fuel</artifactId>
        <version>VERSION</version>
        <scope>test</scope>
    </dependency>
</dependencies>

<repositories>
	<repository>
	    <id>jitpack.io</id>
	    <url>https://jitpack.io</url>
	</repository>
</repositories>

Gradle

Replace VERSION with above latest release version.

repositories {
    maven { url 'https://jitpack.io' }
}

dependencies {
    testCompile 'com.github.KennethWussmann:mock-fuel:VERSION' 
}

Getting started

To setup mock-fuel simply annotate your test class with @ExtendWith:

@ExtendWith(MockFuelExtension::class)
internal class MyAwesomeTest 

Now, every time Fuel is emitting requests mock-fuel will catch them and send mock responses instead.

To change what mock-fuel returns you add MockFuelStore as a parameter to your test in the class that is annotated with the above annotation:

@ExtendWith(MockFuelExtension::class)
internal class MyAwesomeTest {
    @Test
    fun `Should use mock-fuel`(mockFuelStore: MockFuelStore) {
       // mockFuelStore contains every response that is served to incoming requests.
       // How to use it is described below.
    }
}

Enqueue

Enqueuing is the simplest. You just put a response to a queue and every time Fuel is doing a request the first element of the queue will be served as response. Enqueued responses will be removed from the queue when they were served to a request.

@Test
fun `Should return expected body without doing real http requests`(mockFuelStore: MockFuelStore) {
    val expectedBody = """
        {
            "hello": "world"
        }
    """.trimIndent()

    // we enqueue this response to be served when any request comes in
    mockFuelStore.enqueue(
            MockResponse(
                    statusCode = 200,
                    body = expectedBody.toByteArray()
            )
    )

    // make any request
    val (_, response, result) = Fuel.get("/test").responseString()

    assertEquals(200, response.statusCode)
    assertEquals(expectedBody, result.get())
}

Dispatching

Dispatching allows you to serve certain responses when expected requests where made.

@Test
fun `Should return response for certain request`(mockFuelStore: MockFuelStore) {
    mockFuelStore.on(method = Method.POST, path = "/test") {
            MockResponse(
                    statusCode = 200,
                    body = """{ "success": true }""".toByteArray()
            )
    }

    val (_, response, result) = Fuel
            .post("/test")
            .body("{}")
            .responseString()

    assertEquals(200, response.statusCode)
    assertEquals("""{ "success": true }""", result.get())
}

Pass through responses

Sometimes you might want Fuel to actually send a request. You can do that with the PassThroughResponse like this:

mockFuelStore enqueue PassThroughResponse

// mock-fuel will not intercept the next request
Fuel.get("http://example.com/test").responseString()

// but you can still use mock-fuel to verify the request structure
mockFuelStore.verifyRequest {
    assertPath("/test")
}

You can use PassThroughResponse everywhere where you could use a MockResponse - including Dispatching

Verify requests

mock-fuel comes with some utility assertions to ensure that the request that was sent meets certain requirements.

@Test
fun `Should ensure request structure`(mockFuelStore: MockFuelStore) {
    mockFuelStore.enqueue(MockResponse(statusCode = 200))
    
    val (_, response, _) = Fuel
            .post("/test", parameters = listOf("abc" to "123", "abc" to "456"))
            .body("{}")
            .header("X-Example" to "hello_world")
            .responseString()
    
    // verify that there was a request.
    // requests are also in a queue and calling verifyRequest takes & removes the first
    mockFuelStore.verifyRequest {
        // it should be a POST
        assertMethod(Method.POST)
        // to /test
        assertPath("/test")
        // with the queryParameter abc=123 & abc=456
        assertQueryParams("abc", listOf("123", "456"))
        // and the header X-Example: hello_world
        assertHeader("X-Example", "hello_world")
        // more assertions available ...
    }
    
    assertEquals(200, response.statusCode)
}