Search code examples
javaunit-testingmockitoundertow

java: unit test lambda expression inside dispatcher


I have a class:

public class RequestHandler implements HttpHandler {
  public void handleRequest(HttpServerExchange serverContext) throws Exception {
    serverContext.dispatch(() -> serverContext.getRequestReceiver()
        .receiveFullBytes((httpServerExchange, reqBytes) -> {

          // business logic along with few function call

        }
      )
    );

  }
} 

I want to write a unit test case to test my business logic. I am not sure how to do it with 2 levels of a lambda expression insider a dispatcher? Can someone please suggest a good way to write test cases?

I know that we can move business logic to new class and can test it (i guess it's better designed) but curious to know what if it's part of some legacy code or something that we can't change, how can we test it?


Solution

  • Under the assumption that somewhere in your buisness logic you forward the received message (or whatever you do with it) to somewhere else, you can just test your code as usual.

    Note that HttpServerExchange is a final class, so you need to use a Mockito version that supports final mocking - and you have to enable it, as described here.

    To get around the lambda expression you need to use thenAnswer or doAnswer to trigger the invocation of the correct interface method manually.

    A simple example could look like this:

    import org.junit.jupiter.api.Assertions;
    import org.junit.jupiter.api.Test;
    import org.junit.jupiter.api.extension.ExtendWith;
    import org.mockito.ArgumentCaptor;
    import org.mockito.InjectMocks;
    import org.mockito.Mock;
    import org.mockito.Mockito;
    import org.mockito.junit.jupiter.MockitoExtension;
    import org.mockito.stubbing.Answer;
    
    import io.undertow.io.Receiver;
    import io.undertow.io.Receiver.FullBytesCallback;
    import io.undertow.server.HttpHandler;
    import io.undertow.server.HttpServerExchange;
    
    @ExtendWith(MockitoExtension.class)
    public class RequestHandlerTest {
    
        static class BuisnessLogic {
            public void someMethod(HttpServerExchange httpServerExchange, byte[] reqBytes) {
            }
        }
    
        static class RequestHandler implements HttpHandler {
    
            BuisnessLogic logic;
    
            public void handleRequest(HttpServerExchange serverContext) throws Exception {
                serverContext.dispatch(
                    () -> serverContext.getRequestReceiver().receiveFullBytes(
                        (httpServerExchange, reqBytes) -> {
                            logic.someMethod(httpServerExchange, reqBytes);
                        }
                    )
                );
            }
        }
    
        @Mock
        BuisnessLogic logic;
    
        @InjectMocks
        RequestHandler handler;
    
        @Test
        public void test() throws Exception {
    
            byte[] message = new byte[] {1,2,3};
            HttpServerExchange serverContext = Mockito.mock(HttpServerExchange.class);
    
            // 1st lambda
            Mockito.when(serverContext.dispatch(Mockito.any(Runnable.class)))
                   .thenAnswer((Answer<HttpServerExchange>) invocation -> {
    
                Runnable runnable = invocation.getArgument(0);
                runnable.run();
    
                return serverContext;
            });
    
            // 2nd lambda
            Receiver receiver = Mockito.mock(Receiver.class);
            Mockito.doAnswer((Answer<Void>) invocation -> {
    
                FullBytesCallback callback = invocation.getArgument(0);
                callback.handle(serverContext, message);
    
                return null;
    
            }).when(receiver).receiveFullBytes(Mockito.any(FullBytesCallback.class));
    
            Mockito.when(serverContext.getRequestReceiver()).thenReturn(receiver);
    
            // class under test - method invocation
            handler.handleRequest(serverContext);
    
            // buisness logic call verification
            ArgumentCaptor<HttpServerExchange> captor1 = ArgumentCaptor.forClass(HttpServerExchange.class);
            ArgumentCaptor<byte[]> captor2 = ArgumentCaptor.forClass(byte[].class);
    
            Mockito.verify(logic).someMethod(captor1.capture(), captor2.capture());
    
            Assertions.assertEquals(serverContext, captor1.getValue());
            Assertions.assertEquals(message, captor2.getValue());
        }
    }
    

    As others already mentioned you should only use that approach for legacy code.

    A simple refactoring could just push the entire part you need to test into its own method, which - in the example above - would just be the buisness logic itself.

    There is no explicit need to test the undertow framework yourself.