Unit testing Armeria's decorator using context.log().whenComplete()

270 Views Asked by At

I have a subclass of SimpleDecoratingHttpService that contains something like this:

    override fun serve(ctx: ServiceRequestContext, req: HttpRequest): HttpResponse {
        ctx.log().whenComplete().thenAccept {
            if (it.responseCause() == ...) {
                // do stuff
            }
        }
        return unwrap().serve(ctx, req)
    }

I want to test the logic inside the whenComplete() callback. However, when writing tests like this:

myDecorator.serve(context, request).aggregate().join()

the log() future never completes. What do I need to do to ensure that the log() future eventually completes?

1

There are 1 best solutions below

0
On BEST ANSWER

Emulating RequestLog completion

A RequestLog is completed by Armeria's networking layer, so just consuming an HttpRequest or HttpResponse will not complete a RequestLog. To complete it, you need to call the methods in RequestLogBuilder:

var myDecorator = new MySimpleDecoratingHttpService(...);

var ctx = ServiceRequestContext.of(
    HttpRequest.of(HttpMethod.GET, "/hello"));
var req = ctx.request();

var res = myDecorator.serve(ctx, ctx.req).aggregate().join();

// Fill the log.
ctx.logBuilder().endRequest();
assert ctx.log().isRequestComplete();

ctx.logBuilder().responseHeaders(ResponseHeaders.of(200));
ctx.logBuilder().endResponse();

assert ctx.log().isComplete();

Armeria team uses the same technique for testing BraveService, so you might want to check it out as well at BraveServiceTest.java:161.

Testing with a real server

If your setup is too complex to use a mock, as an alternative approach, you can launch a real Armeria server so that Armeria fills the log for you. You can easily launch a server using ServerRule (JUnit 4) or ServerExtension (JUnit 5):

class MyJUnit5Test {
  static final var serviceContexts =
      new LinkedBlockingQueue<ServiceRequestContext>();

  @RegisterExtension
  static final var server = new ServerExtension() {
    @Override
    protected void configure(ServerBuilder sb) throws Exception {
      sb.service("/hello", (ctx, req) -> HttpResponse.of(200));
      sb.decorator(delegate -> new MySimpleDecoratingHttpService(delegate, ...));

      // Record the ServiceRequestContext of each request.
      sb.decorator((delegate, ctx, req) -> {
        serviceContexts.add(ctx);
        return delegate.serve(ctx, req);
      });
    }
  };

  @BeforeEach
  void clearServiceContexts() {
    serviceContexts.clear();
  }

  @Test
  void test() {
    // Send a real request.
    var client = WebClient.of(server.httpUri());
    var res = client.get("/hello").aggregate().join();

    // Get the ServiceRequestContext and its log.
    var ctx = serviceContexts.take();
    var log = sctx.log().whenComplete().join();

    // .. check `log` here ..
    assertEquals(200, log.responseHeaders().status().code());
  }
}