2.4 Creating a Client
As mentioned previously, Micronaut includes both an HTTP server and an HTTP client. A low-level HTTP client is provided which you can use to test the HelloController
created in the previous section.
import io.micronaut.http.HttpRequest;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.runtime.server.EmbeddedServer;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import org.junit.jupiter.api.Test;
import jakarta.inject.Inject;
import static org.junit.jupiter.api.Assertions.assertEquals;
@MicronautTest
public class HelloControllerSpec {
@Inject
EmbeddedServer server; (1)
@Inject
@Client("/")
HttpClient client; (2)
@Test
void testHelloWorldResponse() {
String response = client.toBlocking() (3)
.retrieve(HttpRequest.GET("/hello"));
assertEquals("Hello World", response); (4)
}
}
import io.micronaut.http.HttpRequest
import io.micronaut.http.client.HttpClient
import io.micronaut.http.client.annotation.Client
import io.micronaut.runtime.server.EmbeddedServer
import io.micronaut.test.extensions.spock.annotation.MicronautTest
import spock.lang.Specification
import jakarta.inject.Inject
@MicronautTest
class HelloControllerSpec extends Specification {
@Inject
EmbeddedServer embeddedServer (1)
@Inject
@Client("/")
HttpClient client (2)
void "test hello world response"() {
expect:
client.toBlocking() (3)
.retrieve(HttpRequest.GET('/hello')) == "Hello World" (4)
}
}
import io.micronaut.http.client.HttpClient
import io.micronaut.http.client.annotation.Client
import io.micronaut.runtime.server.EmbeddedServer
import io.micronaut.test.extensions.junit5.annotation.MicronautTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import jakarta.inject.Inject
@MicronautTest
class HelloControllerSpec {
@Inject
lateinit var server: EmbeddedServer (1)
@Inject
@field:Client("/")
lateinit var client: HttpClient (2)
@Test
fun testHelloWorldResponse() {
val rsp: String = client.toBlocking() (3)
.retrieve("/hello")
assertEquals("Hello World", rsp) (4)
}
}
1 | The EmbeddedServer is configured as a shared test field |
2 | A HttpClient instance shared field is also defined |
3 | The test uses the toBlocking() method to make a blocking call |
4 | The retrieve method returns the controller response as a String |
In addition to a low-level client, Micronaut features a declarative, compile-time HTTP client, powered by the Client annotation.
To create a client, create an interface annotated with @Client
, for example:
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.annotation.Client;
import org.reactivestreams.Publisher;
import io.micronaut.core.async.annotation.SingleResult;
@Client("/hello") (1)
public interface HelloClient {
@Get(consumes = MediaType.TEXT_PLAIN) (2)
@SingleResult
Publisher<String> hello(); (3)
}
import io.micronaut.http.annotation.Get
import io.micronaut.http.client.annotation.Client
import org.reactivestreams.Publisher
import io.micronaut.core.async.annotation.SingleResult
@Client("/hello") (1)
interface HelloClient {
@Get(consumes = MediaType.TEXT_PLAIN) (2)
@SingleResult
Publisher<String> hello() (3)
}
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Get
import io.micronaut.http.client.annotation.Client
import io.micronaut.core.async.annotation.SingleResult
import org.reactivestreams.Publisher
@Client("/hello") (1)
interface HelloClient {
@Get(consumes = [MediaType.TEXT_PLAIN]) (2)
@SingleResult
fun hello(): Publisher<String> (3)
}
1 | The @Client annotation is used with a value that is a relative path to the current server |
2 | The same @Get annotation used on the server is used to define the client mapping |
3 | A Publisher annotated with SingleResult is returned with the value read from the server |
To test the HelloClient
, retrieve it from the ApplicationContext associated with the server:
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import org.junit.jupiter.api.Test;
import io.micronaut.core.async.annotation.SingleResult;
import jakarta.inject.Inject;
import reactor.core.publisher.Mono;
import static org.junit.jupiter.api.Assertions.assertEquals;
@MicronautTest (1)
public class HelloClientSpec {
@Inject
HelloClient client; (2)
@Test
public void testHelloWorldResponse(){
assertEquals("Hello World", Mono.from(client.hello()).block());(3)
}
}
import io.micronaut.test.extensions.spock.annotation.MicronautTest
import reactor.core.publisher.Mono
import spock.lang.Specification
import jakarta.inject.Inject
@MicronautTest (1)
class HelloClientSpec extends Specification {
@Inject HelloClient client (2)
void "test hello world response"() {
expect:
Mono.from(client.hello()).block() == "Hello World" (3)
}
}
import io.micronaut.context.annotation.Property
import io.micronaut.test.extensions.junit5.annotation.MicronautTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import jakarta.inject.Inject
import reactor.core.publisher.Mono
@MicronautTest (1)
class HelloClientSpec {
@Inject
lateinit var client: HelloClient (2)
@Test
fun testHelloWorldResponse() {
assertEquals("Hello World", Mono.from(client.hello()).block())(3)
}
}
1 | The @MicronautTest annotation defines the test |
2 | The HelloClient is injected from the ApplicationContext |
3 | The client is invoked using the Project Reactor Mono::block method |
The Client annotation produces an implementation automatically for you at compile time without the using proxies or runtime reflection.
The Client annotation is very flexible. See the section on the Micronaut HTTP Client for more information.