Spring boot 3.2.3 Java 21

Repro: https://github.com/MV-GH/repro-cat-fact-restclient-mockrestserviceserver

Problem:

RestClient doesn't work with MockRestServiceServer

I had working code using RestTemplate. I replaced these with RestClient but I couldn't get the MockRestServiceServer to work. It seems that MockService is not properly being injected into the RestClient.

Stacktrace

jakarta.servlet.ServletException: Request processing failed: org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://localhost:8080/fact": null

    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1022)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903)
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
    at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:72)
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)
    at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:165)
    at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:132)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
    at org.springframework.test.web.servlet.setup.MockMvcFilterDecorator.doFilter(MockMvcFilterDecorator.java:151)
    at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:132)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
    at org.springframework.test.web.servlet.setup.MockMvcFilterDecorator.doFilter(MockMvcFilterDecorator.java:151)
    at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:132)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
    at org.springframework.test.web.servlet.setup.MockMvcFilterDecorator.doFilter(MockMvcFilterDecorator.java:151)
    at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:132)
    at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:201)
    at com.example.demo.CatFactServiceTest.test(CatFactServiceTest.java:45)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
Caused by: org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://localhost:8080/fact": null
    at org.springframework.web.client.DefaultRestClient$DefaultRequestBodyUriSpec.createResourceAccessException(DefaultRestClient.java:557)
    at org.springframework.web.client.DefaultRestClient$DefaultRequestBodyUriSpec.exchangeInternal(DefaultRestClient.java:482)
    at org.springframework.web.client.DefaultRestClient$DefaultRequestBodyUriSpec.retrieve(DefaultRestClient.java:444)
    at com.example.demo.CatFactService.getFact(CatFactService.java:17)
    at com.example.demo.CatFactController.getFact(CatFactController.java:17)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:259)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:192)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:920)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:830)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
    ... 24 more
Caused by: java.net.ConnectException
    at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:951)
    at java.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:133)
    at org.springframework.http.client.JdkClientHttpRequest.executeInternal(JdkClientHttpRequest.java:102)
    at org.springframework.http.client.AbstractStreamingClientHttpRequest.executeInternal(AbstractStreamingClientHttpRequest.java:70)
    at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:66)
    at org.springframework.web.client.DefaultRestClient$DefaultRequestBodyUriSpec.exchangeInternal(DefaultRestClient.java:476)
    ... 38 more
Caused by: java.net.ConnectException
    at java.net.http/jdk.internal.net.http.common.Utils.toConnectException(Utils.java:1028)
    at java.net.http/jdk.internal.net.http.PlainHttpConnection.connectAsync(PlainHttpConnection.java:227)
    at java.net.http/jdk.internal.net.http.PlainHttpConnection.checkRetryConnect(PlainHttpConnection.java:280)
    at java.net.http/jdk.internal.net.http.PlainHttpConnection.lambda$connectAsync$2(PlainHttpConnection.java:238)
    at java.base/java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:934)
    at java.base/java.util.concurrent.CompletableFuture$UniHandle.tryFire(CompletableFuture.java:911)
    at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
    at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
    at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: java.nio.channels.ClosedChannelException
    at java.base/sun.nio.ch.SocketChannelImpl.ensureOpen(SocketChannelImpl.java:202)
    at java.base/sun.nio.ch.SocketChannelImpl.beginConnect(SocketChannelImpl.java:786)
    at java.base/sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:874)
    at java.net.http/jdk.internal.net.http.PlainHttpConnection.lambda$connectAsync$1(PlainHttpConnection.java:210)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:571)
    at java.net.http/jdk.internal.net.http.PlainHttpConnection.connectAsync(PlainHttpConnection.java:212)
    ... 9 more

Comment From: bclozel

I don't think this is a valid setup and I'm not sure this would even work with RestTemplate. The MockRestServiceServer bind operation modifies the builder to set up a client factory that dispatches request to the mockn but the CatFactService is injected with a separate builder: in Spring Boot, all RestClient.Builder instances are prototype scoped, meaning that each injection point gets its own new instance - mutating one doesn't affect the others.

I would suggest either writing a full integration test with @SpringBootTest and a mock server or testing your service independently (without calling the controller) and test the REST client.

Comment From: MV-GH

This does work with RestTemplate see this branch for its equivalent with RestTemplate.

Neither of those options solve the problem that I need MockRestServiceServer because I need to mock a thirdparty API nested within my service. In the sample that is https://catfact.ninja

I have made SO post, since I can't find any documentation on MockRestServiceServer with RestClient https://stackoverflow.com/questions/78203010/how-to-use-mockrestserviceserver-with-restclient-in-spring-boot