Anadi Misra
Anadi Misra

Reputation: 1

mocking handler filter function gives null mapper error in tests

I'm trying to mock a HandlerFilterFunction implementation of a filter attached to a RouterFunction, which has nested paths, to tests paths are invoking the correct function for handling requests. But I get a null pointer exception in the test, looks like the mocks aren't invoked.

I have defined a Router function as follows

@Slf4j
@Configuration
@RequiredArgsConstructor(onConstructor_ = {@Autowired})
public class FormRouter {
    private final XFabricCorrelationHeaderFilter xFabricCorrelationHeaderFilter;
    private final ValidFormFilter validFormFilter;
    private final FormHandler formHandler;
    private final TabHandler tabHandler;
    private final BlockHandler blockHandler;

    @Setter(onMethod_ = {@Value("${form.url:/forms}")})
    String serviceBaseURL;

    @Bean
    @FormRouterApiDocs
    public RouterFunction<ServerResponse> formRoutes() {
        return RouterFunctions.route()
                .path(serviceBaseURL + "/{formId}", builder ->
                        builder.nest(accept(APPLICATION_JSON), nestedBuilder ->
                                nestedBuilder.path("/tabs", tabBuilder ->
                                            tabBuilder.nest(accept(APPLICATION_JSON), nestedTabBuilder ->
                                                nestedTabBuilder.path("/{tabId}/blocks", blocksHandlerBuilder ->
                                                    blocksHandlerBuilder.GET(blockHandler::getBlocks)
                                                    .PUT(blockHandler::postBlock))
                                                    .PUT(tabHandler::postTab)
                                                    .GET(tabHandler::getTabs)))
                                    .GET(formHandler::getFormWithFormId)
                                    .PUT(formHandler::putForm)
                                    .DELETE(formHandler::deleteForm)
                                    .filter(validFormFilter)))
                .path(serviceBaseURL, b ->
                        b.nest(accept(APPLICATION_JSON), nestedBuilder ->
                                nestedBuilder.POST(formHandler::postForm)
                                        .GET(formHandler::getFormList)))
                .filter(xFabricCorrelationHeaderFilter)
                .build();
    }
}

The filter is defined as follows

@Slf4j
@Component
@RequiredArgsConstructor
public class ValidFormFilter implements HandlerFilterFunction<ServerResponse, ServerResponse> {

    private final FormService formService;

    @Override
    public @NonNull Mono<ServerResponse> filter(ServerRequest request, @NonNull HandlerFunction<ServerResponse> next) {
        HttpMethod method = request.method();
        String path = request.path();
        if (method == HttpMethod.PUT || method == HttpMethod.PATCH || method == HttpMethod.GET || method == HttpMethod.DELETE) {
            XFabricCorrelation xFabricCorrelation = XFabricCorrelationWebflux.fromServerRequest(request);
            String formId = request.pathVariable("formId");
            return formService.lookFormform(blah)
                    .doOnError(blah blah)
                    .flatMap(form -> add form as request attribute and return request)
                    .flatMap(next::handle)
                    .log(this.getClass().getName(), Level.FINE);
        }
        return next.handle(request);
    }
}

and then FormHandler

@Slf4j
@Component
@RequiredArgsConstructor
public class FormHandler {
    ... // dependencies

    public Mono<ServerResponse> getFormWithFormId(ServerRequest serverRequest){
        // do stuff and return response
    }

I want to mock the filter, since I want to ensure that the route /forms/{formId}/tabs isn't matched to /forms/{formId} and also document this behaviour for other developers. Here's the test code

@Slf4j
@SpringJUnitConfig({FormRouter.class, CorrelationHeaderFilter.class, CommonHeaderInfo.class})
@TestPropertySource(inheritLocations = false, locations = {"classpath:webflux-test.properties"})
@DisplayName("Form API Routes")
@MockBeans({@MockBean(FormHandler.class), @MockBean(TabHandler.class), @MockBean(BlockHandler.class), @MockBean(ValidFormFilter.class)})
class FormRoutesTest {
    ...
    @Autowired
    FormRouter formRouter;

    @Autowired
    FormHandler handler;
    @Autowired
    TabHandler tabHandler;
    @Autowired
    BlockHandler blockHandler;
    @Autowired
    ValidFormFilter formFilter;

    WebTestClient webTestClient;
    Form form;

    @BeforeEach
    void setUp() {
        fabricHeader = String.join(":", realmId, accountId, workspaceId);
        webTestClient = WebTestClient.bindToRouterFunction(formRouter.formRoutes()).configureClient().baseUrl(formRouter.serviceBaseURL).build();
        log.debug("Registered Web Client on URL {}", formRouter.serviceBaseURL);
        form = FormTestData.Given.aForm("1", realmId, accountId, workspaceId);
        log.debug("Registered Form Service Mocks");
    }

    ...

    @Test
    @DisplayName("THEN invokes getting form by Id")
    void testGetFormWithFormIdWhenValidRequestHeader() {
        when(handler.getFormWithFormId(any(ServerRequest.class))).thenReturn(ServerResponse.ok().build());
        when(formFilter.filter(any(ServerRequest.class), any()))
                .thenAnswer(invocation -> {
                    ServerRequest request = invocation.getArgument(0);
//                        HandlerFunction<ServerResponse> next = invocation.getArgument(1);
//                        // Simulate calling next.handle(request) in the filter
//                        return next.handle(request);
                    return handler.getFormWithFormId(request);
                });

        webTestClient.get()
                .uri(uriBuilder -> uriBuilder.path("/{formId}").build(form.getFormId()))
                .headers(httpHeaders -> httpHeaders.add(fabricAuthHeader, fabricHeader))
                .exchange()
                .expectStatus().isOk();

        verify(formFilter).filter(any(ServerRequest.class), any());
        verify(handler).getFormWithFormId(any(ServerRequest.class));
        verifyNoMoreInteractions(handler);
    }
}

As you can see I 've tried both, returning next.handle or calling the formHandler method directly but in both cases is get the error

2024-10-21 14:51:17 ERROR [parallel-4] HttpWebHandlerAdapter.handleUnresolvedError:293 - [36ac6c0e] 500 Server Error for HTTP GET "/forms/1"
java.lang.NullPointerException: The mapper [org.springframework.web.reactive.function.server.RouterFunctions$FilteredRouterFunction$$Lambda$795/0x00000008005ab0b0] returned a null value.
    at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:108) ~[reactor-core-3.4.31.jar:3.4.31]
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
    *__checkpoint ⇢ HTTP GET "/forms/1" [ExceptionHandlingWebHandler]

Note that the FormHandler does not implement the HandlerFunction I call methods on the bean directly. The valid form filter simply checks if form exists and fails or calls next.handle(request)

Upvotes: 0

Views: 32

Answers (0)

Related Questions