I have a functional router :
@Bean
public RouterFunction<ServerResponse> greetingRoute(Utilities utils,
AppConfig cfg,
GreetingHandler greetingHandler) {
return RouterFunctions.route()
.path("/api",api -> api
// x-headers validation
.filter((request,next) -> utils.verifyXHeaders(request,cfg,next))
// Customer urls
.nest(accept(MediaType.APPLICATION_JSON),ops -> ops
// Greeting API
.GET("/v2/hello",greetingHandler::hello)
)
.build())
.build();
}
And it's handler function below:
public Mono<ServerResponse> hello(ServerRequest serverRequest) {
Mono<Greeting> greetingResponse = greetingSvc.greetingFunction();
return greetingResponse.flatMap(greeting -> {
BaseResponse brs = BaseResponse.builder()
.responseCode(String.valueOf(HttpStatus.OK.value()))
.responseMsg("Greeting")
.detailedMsg("Greeting Success")
.responseData(greeting)
.build();
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
.bodyValue(brs);
});
}
I am trying to write a unit test for the handler as follows:
@Slf4j
class GreetingHandlerTest {
GreetingSvc greetingSvc;
WebTestClient client;
@BeforeEach
void setUp(){
this.greetingSvc = mock(GreetingSvc.class);
Utilities utils = mock(Utilities.class);
AppConfig cfg = mock(AppConfig.class);
Greeting greeting = new Greeting("Hello Spring");
GreetingHandler greetingHandler = new GreetingHandler(this.greetingSvc);
when(this.greetingSvc.greetingFunction()).thenReturn(Mono.just(greeting));
RouterFunction<?> route = new AppRouter()
.greetingRoute(utils,cfg,greetingHandler);
this.client = WebTestClient.bindToRouterFunction(route)
.build();
}
@Test
@DisplayName("Get Hello Back")
void testHello() {
client.get()
.uri("/api/v2/hello")
.headers(Utilities.getHttpHeaders())
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody(BaseResponse.class);
;
}
}
However this test always fails with an error The mapper returned a null Mono
and the following stack trace:
12:46:14.282 [parallel-1] DEBUG org.springframework.web.server.adapter.HttpWebHandlerAdapter - [73a1446b] HTTP GET "/api/v2/hello"
12:46:14.334 [parallel-1] ERROR org.springframework.web.server.adapter.HttpWebHandlerAdapter - [73a1446b] 500 Server Error for HTTP GET "/api/v2/hello"
java.lang.NullPointerException: The mapper returned a null Mono
at java.util.Objects.requireNonNull(Objects.java:228)
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
*__checkpoint ⇢ HTTP GET "/api/v2/hello" [ExceptionHandlingWebHandler]
Original Stack Trace:
at java.util.Objects.requireNonNull(Objects.java:228)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:125)
at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:74)
at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:82)
at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.innerNext(FluxConcatMap.java:282)
at reactor.core.publisher.FluxConcatMap$ConcatMapInner.onNext(FluxConcatMap.java:863)
at reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:200)
at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:122)
at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:82)
at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.innerNext(FluxConcatMap.java:282)
at reactor.core.publisher.FluxConcatMap$ConcatMapInner.onNext(FluxConcatMap.java:863)
at reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:200)
at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:82)
at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:431)
at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onSubscribe(FluxConcatMap.java:219)
at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:201)
at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:83)
at reactor.core.publisher.Mono.subscribe(Mono.java:4490)
at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:451)
at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onSubscribe(FluxConcatMap.java:219)
at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:201)
at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:83)
at reactor.core.publisher.Mono.subscribe(Mono.java:4490)
at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:451)
at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onSubscribe(FluxConcatMap.java:219)
at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:201)
at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:83)
at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:53)
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:53)
at reactor.core.publisher.Mono.subscribe(Mono.java:4490)
at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:263)
at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:51)
at reactor.core.publisher.Mono.subscribe(Mono.java:4490)
at reactor.core.publisher.Mono.subscribeWith(Mono.java:4605)
at reactor.core.publisher.Mono.subscribe(Mono.java:4457)
at reactor.core.publisher.Mono.subscribe(Mono.java:4393)
at org.springframework.test.web.reactive.server.HttpHandlerConnector.lambda$doConnect$2(HttpHandlerConnector.java:98)
at org.springframework.mock.http.client.reactive.MockClientHttpRequest.lambda$null$2(MockClientHttpRequest.java:124)
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:45)
at reactor.core.publisher.Mono.subscribe(Mono.java:4490)
at reactor.core.publisher.FluxConcatIterable$ConcatIterableSubscriber.onComplete(FluxConcatIterable.java:147)
at reactor.core.publisher.FluxConcatIterable.subscribe(FluxConcatIterable.java:60)
at reactor.core.publisher.Mono.subscribe(Mono.java:4490)
at reactor.core.publisher.Mono.subscribeWith(Mono.java:4605)
at reactor.core.publisher.Mono.subscribe(Mono.java:4457)
at reactor.core.publisher.Mono.subscribe(Mono.java:4393)
at org.springframework.test.web.reactive.server.HttpHandlerConnector.doConnect(HttpHandlerConnector.java:112)
at org.springframework.test.web.reactive.server.HttpHandlerConnector.lambda$connect$0(HttpHandlerConnector.java:79)
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:45)
at reactor.core.publisher.Mono.subscribe(Mono.java:4490)
at reactor.core.publisher.MonoSubscribeOn$SubscribeOnSubscriber.run(MonoSubscribeOn.java:126)
at reactor.core.scheduler.WorkerTask.call(WorkerTask.java:84)
at reactor.core.scheduler.WorkerTask.call(WorkerTask.java:37)
at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
at java.util.concurrent.FutureTask.run(FutureTask.java)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
12:46:14.346 [parallel-1] DEBUG org.springframework.test.web.reactive.server.HttpHandlerConnector - Creating client response for GET "/api/v2/hello"
12:46:14.351 [parallel-1] DEBUG org.springframework.web.reactive.function.client.ExchangeFunctions - [68105edc] [185decc2] Response 500 INTERNAL_SERVER_ERROR
But testing the api on postman it responds with a success:
{
"responseCode": "200",
"responseMsg": "Greeting",
"detailedMsg": "Greeting Success",
"Data": {
"message": "Hello, Spring!"
}
}
Been stuck on this for a while now. How do I make this unit test pass by mocking the greeting service implementation. The Integration test also works by the way, am stuck on this unit test.
For more context greetingSvc
implementation is here:
public Mono<Greeting> greetingFunction(){
return Mono.just(new Greeting("Hello, Spring!"));
}
The problem here was trying to mock the router params . In my case I replaced mocks for Utilities
and AppConfig
with real initialised objects as follows:
Utilities utilities = new Utilities();
AppConfig cfg = new AppConfig();
I updated my test to the following code and it passed.
@Slf4j
class GreetingHandlerTest {
GreetingSvc greetingSvc;
WebTestClient client;
@BeforeEach
void setUp(){
this.greetingSvc = mock(GreetingSvc.class);
Utilities utilities = new Utilities();
AppConfig cfg = new AppConfig();
Greeting greeting = new Greeting("Hello Spring");
GreetingHandler greetingHandler = new GreetingHandler(this.greetingSvc);
when(this.greetingSvc.greetingFunction()).thenReturn(Mono.just(greeting));
RouterFunction<?> route = new AppRouter().greetingRoute(cfg,utilities,greetingHandler);
this.client = WebTestClient.bindToRouterFunction(route)
.build();
}
@Test
@DisplayName("Get Hello Back")
void testHello() {
client.get()
.uri("/api/v2/hello")
.headers(Utilities.getHttpHeaders())
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody(BaseResponse.class);
;
}
}