Micronaut HTTP Client - Parsing Response

I am using Micronaut 2.0 in this post. You can verify your version using the mn -Version command.

Let us generate a controller using the create-controller command provided by the Micronaut CLI as follows. I am launching the CLI from myapp directory.

➜  myapp mn
mn> create-controller com.nareshak.demo.Greeting
| Rendered controller to src/main/java/com/nareshak/demo/GreetingController.java
| Rendered test to src/test/groovy/com/nareshak/demo/GreetingControllerSpec.groovy

Note how Micronaut suffixed Controller to the value you supplied. Also, it generated a class to hold the corresponding tests. In this case, I am using the Spock framework for my tests (specified during the project creation). The generated test class looks as follows.

package com.nareshak.demo

import io.micronaut.http.client.annotation.Client
import io.micronaut.runtime.server.EmbeddedServer
import io.micronaut.test.annotation.MicronautTest
import io.micronaut.http.client.RxHttpClient
import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
import spock.lang.AutoCleanup
import spock.lang.Specification
import spock.lang.Shared

import javax.inject.Inject

@MicronautTest
class GreetingControllerSpec extends Specification {

    @Shared @Inject
    EmbeddedServer embeddedServer

    @Shared @AutoCleanup @Inject @Client("/")
    RxHttpClient client

    void "test index"() {
        given:
        HttpResponse response = client.toBlocking().exchange("/greeting")

        expect:
        response.status == HttpStatus.OK
    }
}

At this point, if you try to run the test, it should pass.

Lets's go to the generated controller class GreetingController and replace the return value from "Example Response" to "Hello". Your controller should now look as follows.

package com.nareshak.demo;

import io.micronaut.http.annotation.*;

@Controller("/greeting")
public class GreetingController {

    @Get(uri="/", produces="text/plain")
    public String index() {
        return "Hello";
    }
}

In our tests, we want to make sure that the GET call to endpoint "/greeting" indeed returns the value "Hello". So let's express that in our tests.

@MicronautTest
class GreetingControllerSpec extends Specification {

    @Shared @Inject
    EmbeddedServer embeddedServer

    @Shared @AutoCleanup @Inject @Client("/")
    RxHttpClient client

    void "test index"() {
        given:
        HttpResponse response = client.toBlocking().exchange("/greeting")

        expect:
        response.status == HttpStatus.OK
        and:
        response.getContentType().get() == MediaType.TEXT_PLAIN_TYPE
        and:
        response.getBody(String).get() == 'Hello'
    }
}

The response object, which is of type io.micronaut.http.HttpResponse has a method getBody. It accepts the type(class) of response. Since we are expecting a String value, we pass String as the argument. getBody returns a java.util.Optional type upon which I am calling get to fetch the value "Hello". However, when you run the test, you get the following failure message.

Condition failed with Exception:

response.getBody(String).get() == 'Hello'
|        |       |       |
|        |       |       java.util.NoSuchElementException: No value present
|        |       |       	at java.base/java.util.Optional.get(Optional.java:148)
|        |       |       	at com.nareshak.demo.GreetingControllerSpec.test index(GreetingControllerSpec.groovy:34)
|        |       class java.lang.String
|        Optional.empty
<io.micronaut.http.client.netty.FullNettyClientHttpResponse@5db948c9 status=OK headers=io.micronaut.http.netty.NettyHttpHeaders@357c9bd9 attributes={} nettyHttpResponse=HttpObjectAggregator$AggregatedFullHttpResponse(decodeResult: success, version: HTTP/1.1, content: CompositeByteBuf(freed, components=1))
HTTP/1.1 200 OK

Surprising! Isn't' it? The response body doesn't contain any value.

Upon inspecting the API io.micronaut.http.client.BlockingHttpClient, we find multiple overloaded methods with the name exchange. The second argument we can pass to the method is bodyType. If we don't specify one, the default value null is assumed.

Since we didn't specify a bodyType value, Micronaut HTTP client interpreted it as we are not interested in the response body and didn't put any efforts to parse the response. After all, being lazy is a trait of intelligence in the world of reactive-programming!

Let's supply that second argument to exchange method and re-run our tests.

void "test index"() {
    given:
    HttpResponse response = client.toBlocking().exchange("/greeting", String)

    expect:
    response.status == HttpStatus.OK
    and:
    response.getContentType().get() == MediaType.TEXT_PLAIN_TYPE
    and:
    response.getBody(String).get() == 'Hello'
}

Now our tests are passing. If you have faced this problem already, you may be screaming - "Eureka, body found!"

In most common cases your return type would be a Java class representing the structure of your response.

If you just want to get the response body and thinking it's a lot of work, checkout this post.

Micronaut version used: 2.0.0