Im struggling with testing controller of WebFlux app. Test always returns 500 even though I mock status 404. I read that adding onErrorResume solves this problem because otherwise error is handled in AbstractErrorWebExceptionHandler but this doesn't solve my problem. Endpoints work fine when working working with them in swagger.
Service code
return githubWebClient.get()
.uri(("/users/{username}/repos"), username)
.retrieve()
.onStatus(httpStatusCode -> httpStatusCode.value() == 404,
error -> Mono.error(new GithubUserNotFoundException("Github api did not find user: " + username)))
.bodyToFlux(Repository.class)
.onErrorMap(Throwable.class, throwable -> new Exception("plain exception"))
.flatMap(this::fetchBranchesForRepository)
.filter(repository -> !repository.fork());
}
Controller advice code
@RestControllerAdvice
public class RestControllerHandler {
@ExceptionHandler(GithubUserNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ExceptionMessage handleGithubUserNotFoundException(GithubUserNotFoundException ex){
ExceptionMessage exceptionMessage = ExceptionMessage
.builder()
.message(ex.getMessage())
.status(404)
.build();
log.error("Exception has been occurred", ex);
return exceptionMessage;
}
Test code
@Test
void shouldThrowException_WhenApiCantFindUser() {
String user = "test";
WireMock.stubFor(WireMock.get(WireMock.urlEqualTo("/users/" + user + "/repos"))
.willReturn(WireMock.notFound()));
webTestClient.get()
.uri(user)
.exchange()
.expectStatus()
.isNotFound();
}
Console logs
2024-02-16T11:34:38.652+01:00 INFO 92904 --- [ parallel-1] pl.hubert.task.client.GithubClient : Fetching repositories for user: test
2024-02-16T11:34:38.749+01:00 ERROR 92904 --- [ parallel-1] i.n.r.d.DnsServerAddressStreamProviders : Unable to load io.netty.resolver.dns.macos.MacOSDnsServerAddressStreamProvider, fallback to system defaults. This may result in incorrect DNS resolutions on MacOS. Check whether you have a dependency on 'io.netty:netty-resolver-dns-native-macos'. Use DEBUG level to see the full stack: java.lang.UnsatisfiedLinkError: failed to load the required native library
2024-02-16T11:34:38.888+01:00 ERROR 92904 --- [ctor-http-nio-3] o.s.w.s.adapter.HttpWebHandlerAdapter : [63e1e91b] 500 Server Error for HTTP GET "/api/github/test"
java.lang.Exception: plain exception
at pl.hubert.task.client.GithubClient.lambda$getUserRepositories$2(GithubClient.java:33) ~[main/:na]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
*__checkpoint ⇢ Handler pl.hubert.task.controller.GithubController#getGithubReposByUsername(String) [DispatcherHandler]
*__checkpoint ⇢ HTTP GET "/api/github/test" [ExceptionHandlingWebHandler]
Original Stack Trace:
at pl.hubert.task.client.GithubClient.lambda$getUserRepositories$2(GithubClient.java:33) ~[main/:na]
at reactor.core.publisher.Flux.lambda$onErrorMap$28(Flux.java:7239) ~[reactor-core-3.6.2.jar:3.6.2]
at reactor.core.publisher.Flux.lambda$onErrorResume$29(Flux.java:7292) ~[reactor-core-3.6.2.jar:3.6.2]
at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:94) ~[reactor-core-3.6.2.jar:3.6.2]
at reactor.core.publisher.MonoFlatMapMany$FlatMapManyInner.onError(MonoFlatMapMany.java:256) ~[reactor-core-3.6.2.jar:3.6.2]
at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onError(Operators.java:2236) ~[reactor-core-3.6.2.jar:3.6.2]
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onError(MonoFlatMap.java:180) ~[reactor-core-3.6.2.jar:3.6.2]
at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:106) ~[reactor-core-3.6.2.jar:3.6.2]
at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onError(MonoIgnoreThen.java:280) ~[reactor-core-3.6.2.jar:3.6.2]
at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:232) ~[reactor-core-3.6.2.jar:3.6.2]
at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onComplete(MonoIgnoreThen.java:204) ~[reactor-core-3.6.2.jar:3.6.2]
at reactor.core.publisher.FluxOnErrorReturn$ReturnSubscriber.onComplete(FluxOnErrorReturn.java:169) ~[reactor-core-3.6.2.jar:3.6.2]
at reactor.core.publisher.MonoIgnoreElements$IgnoreElementsSubscriber.onComplete(MonoIgnoreElements.java:89) ~[reactor-core-3.6.2.jar:3.6.2]
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onComplete(FluxMapFuseable.java:152) ~[reactor-core-3.6.2.jar:3.6.2]
at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:144) ~[reactor-core-3.6.2.jar:3.6.2]
at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:260) ~[reactor-core-3.6.2.jar:3.6.2]
at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:144) ~[reactor-core-3.6.2.jar:3.6.2]
at reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:415) ~[reactor-netty-core-1.1.15.jar:1.1.15]
at reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:446) ~[reactor-netty-core-1.1.15.jar:1.1.15]
at reactor.netty.channel.ChannelOperations.terminate(ChannelOperations.java:500) ~[reactor-netty-core-1.1.15.jar:1.1.15]
at reactor.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:782) ~[reactor-netty-http-1.1.15.jar:1.1.15]
at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:114) ~[reactor-netty-core-1.1.15.jar:1.1.15]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.105.Final.jar:4.1.105.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.105.Final.jar:4.1.105.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.105.Final.jar:4.1.105.Final]
at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103) ~[netty-codec-4.1.105.Final.jar:4.1.105.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.105.Final.jar:4.1.105.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.105.Final.jar:4.1.105.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.105.Final.jar:4.1.105.Final]
at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436) ~[netty-transport-4.1.105.Final.jar:4.1.105.Final]
at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346) ~[netty-codec-4.1.105.Final.jar:4.1.105.Final]
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:318) ~[netty-codec-4.1.105.Final.jar:4.1.105.Final]
at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251) ~[netty-transport-4.1.105.Final.jar:4.1.105.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442) ~[netty-transport-4.1.105.Final.jar:4.1.105.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.105.Final.jar:4.1.105.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.105.Final.jar:4.1.105.Final]
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) ~[netty-transport-4.1.105.Final.jar:4.1.105.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) ~[netty-transport-4.1.105.Final.jar:4.1.105.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.105.Final.jar:4.1.105.Final]
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) ~[netty-transport-4.1.105.Final.jar:4.1.105.Final]
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) ~[netty-transport-4.1.105.Final.jar:4.1.105.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788) ~[netty-transport-4.1.105.Final.jar:4.1.105.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724) ~[netty-transport-4.1.105.Final.jar:4.1.105.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650) ~[netty-transport-4.1.105.Final.jar:4.1.105.Final]
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562) ~[netty-transport-4.1.105.Final.jar:4.1.105.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997) ~[netty-common-4.1.105.Final.jar:4.1.105.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.105.Final.jar:4.1.105.Final]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.105.Final.jar:4.1.105.Final]
at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na]
2024-02-16T11:34:38.910+01:00 ERROR 92904 --- [ Test worker] o.s.t.w.reactive.server.ExchangeResult : Request details for assertion failure:
> GET /api/github/test
> WebTestClient-Request-Id: [1]
No content
< 500 INTERNAL_SERVER_ERROR Internal Server Error
<
0 bytes of content (unknown content-type).
Status expected:<404 NOT_FOUND> but was:<500 INTERNAL_SERVER_ERROR>
Expected :404 NOT_FOUND
Actual :500 INTERNAL_SERVER_ERROR
<Click to see difference>
java.lang.AssertionError: Status expected:<404 NOT_FOUND> but was:<500 INTERNAL_SERVER_ERROR>