Search code examples
armeria

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


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?


Solution

  • 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());
      }
    }