DEV Community

Daniel Castillo
Daniel Castillo

Posted on

Micronaut: Declarative HTTP Client

Currently I'm working on a side project and I decided to use Microunat (if you don't know anything about Microunat here you can read what it is and how it works) and there is a lot of cool stuff within this "new" framework.

Some I like the most is the really low memory footprint, Micronaut Data and the possibility to be compiled to native code using GraalVM. But those are not the only "cool" features this framework has out of the box.

In order to make our lives easier as developers the Microunat guys included a declarative client to easily test our endpoints, also less verbose. Is a very simple to use, first you'll need to create a simple interface to "declare" the operations to be tested and delegate to the application-context the binding using the url and the requested operations.

Let's assume we need to test the get by id operation for users:

@Controller(value = "/user")
class UserController {

  @Inject
  private UserService userService;

  @Get(value = "/{id}")
  public User getById(@PathVariable("id") final Long id) {
    return userService.get(id);s
  } 

}

So, we can use the EmbeddedServer and the HttpClient (or the RxHttpClient) to test the operation, as it shows the following example:

@MicronautTest
class UserControllerTest {

  @Inject
  private EmbeddedServer embeddedServer;

  @Test
  void testGet_WithBlocking() throws MalformedURLException {
    final var url = format("http://%s:%s", embeddedServer.getHost(), 
        embeddedServer.getPort());
    final var client = HttpClient.create(new URL(url));

    final HttpResponse<User> response = client.toBlocking()
        .exchange(HttpRequest.GET("/user/1"), User.class);

    Assertions.assertEquals(response.getStatus(), HttpStatus.OK);
    Assertions.assertNotNull(response.body());
  }
}

As you can see is a lot of things to be done, quite easily nevertheless a lot of things to do. But Is there less verbose way? The answer is yes. Now let's try using the declarative client, first of all we need to create an interface to "declare" the operations to be tested:

@Client(value = "/user")
interface UserClient {

    @Get(value = "/{id}")
    User getById(final Long id);

}

As you can see is only a contract to be satisfied and the annotation @Client will tell to the test application context that we're are creating a HttpClient and it has to look for an implmentation or Controller to satisfy the operations we're requesting.

And now our test-class will look as the following:

@MicronautTest
class UserControllerTest {

  @Inject
  private UserClient userClient;

  @Test
  void testGet_WithBlocking() {
    final var user = userClient.getById(1L);
    Assertions.assertNotNull(user);
  }
}

As you can see is pretty simple and we're avoiding using the server and the creation of the request, we're delegating the binding to the context itself the satisfaction of our contract. Using the declarative client we can improve our code defining a simple contract to be used as based for the implementation and the client.

class UserController {

    @Get("/{id}")
    User getById(final Long id);

}

For the implementation

@Controller(value = "/user")
class UserControllerImpl implements UserController {

  @Inject
  private UserService userService;

  @Override
  public User getById(@PathVariable("id") final Long id) {
    return userService.get(id);
  } 

}

For the test class

@MicronautTest
class UserControllerTest {

  @Client(value = "/user")
  @Inject
  private UserClient userClient;

  @Test
  void testGet_WithBlocking() throws MalformedURLException {
    final var user = userClient.getById(1L);
    Assertions.assertNotNull(user);
  }
}

Leaving us with a version more clear and simplier, avoiding the unecessary classes or interfaces, now we can ensure we're satisfaying exactly the same contract in the implematation and the test.

I'll leave the official documentation for the Declarative Http Client.

Great coding!

Top comments (0)