Reputation: 1
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