We have a web service A
(Java 17
, spring-cloud-starter-openfeign: 3.1.6
, spring-cloud-starter-netflix-eureka-client: 3.1.6
, testcontainers: 1.18.0
, wiremock: 2.35.0
) that calls service B
using openfeign. Since service B
is registered in Eureka we send a call into the B
service using the openfeign client below:
@FeignClient("SESSION-MANAGER")
public interface SessionManagerClient {
@PostMapping(path = "/session/get", consumes = "application/json")
Session getSession(SessionRequstBody sessionRequstBody);
}
Openfeign configuration (we don't have any other openfeign configurations or beans):
feign:
client:
config:
default:
connectTimeout: 600000
readTimeout: 600000
logger-level: basic
This code finds the appropriate service in Eureka by the SESSION-MANAGER
name and sends the request. It works well in the environment.
But I don't quite understand how to mock this /session/get
call (using wiremock) in integration tests. Because there is Eureka (implicitly) between the code call and the openfeign client. So because of it openfeign thinks about the @FeignClient("SESSION-MANAGER")
as a host and sends such request http://SESSION-MANAGER/session/get
leads to this error:
[WARN ] 2023-06-26 13:56:52.069 [simpleMessageListenerContainer-2] o.s.c.l.c.RoundRobinLoadBalancer - No servers available for service: SESSION-MANAGER
[DEBUG] 2023-06-26 13:56:52.070 [simpleMessageListenerContainer-2] o.s.c.o.l.RetryableFeignBlockingLoadBalancerClient - Selected service instance: null
[WARN ] 2023-06-26 13:56:52.070 [simpleMessageListenerContainer-2] o.s.c.o.l.RetryableFeignBlockingLoadBalancerClient - Service instance was not resolved, executing the original request
[ERROR] 2023-06-26 13:56:52.074 [simpleMessageListenerContainer-2] c.o.t.s.s.MyService - Error in getting Session
feign.RetryableException: SESSION-MANAGER executing POST http://SESSION-MANAGER/session/get
at feign.FeignException.errorExecuting(FeignException.java:268)
...
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.net.UnknownHostException: SESSION-MANAGER
at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:567)
...
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:121)
... 42 common frames omitted
[DEBUG] 2023-06-26 13:56:52.386 [SpringApplicationShutdownHook] o.s.w.c.s.GenericWebApplicationContext - Closing org.springframework.web.context.support.GenericWebApplicationContext@748904e8, started on Mon Jun 26 13:56:49 EEST 2023
[INFO ] 2023-06-26 13:56:52.386 [SpringApplicationShutdownHook] o.s.c.n.e.s.EurekaServiceRegistry - Unregistering application MY-SERVICE with eureka with status DOWN
The question is could you please help me understand what should I mock in this case? Should I directly mock a call to the service B
or maybe I even should up the Eureka in Testcontainers and manage it on this level?
Eureka configuration:
eureka:
client:
registerWithEureka: false
fetchRegistry: true
healthcheck:
enabled: false
serviceUrl:
defaultZone: "http://localhost:8761/eureka/"
instance:
statusPageUrl: "http://${eureka.hostname}/info"
healthCheckUrl: "http://${eureka.hostname}/health"
homePageUrl: "http://${eureka.hostname}/"
nonSecurePortEnabled: true
Wiremock:
WIREMOCK_SERVER.stubFor(WireMock.post(WireMock.urlPathEqualTo("/session/get"))
.withRequestBody(WireMock.equalToJson(SESSION_MANAGER_REQUEST_BODY))
.willReturn(WireMock.aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody(SESSION_MANAGER_RESPONSE))
);
@spencergibb shared a hint in a comment.
No. I mean to do something like this github.com/spring-cloud/spring-cloud-gateway/blob/… and this github.com/spring-cloud/spring-cloud-gateway/blob/… in your test. Disable eureka, but plug into the load balancer framework to point to a preconfigured HTTP service.
I will put my solution here to make it answer visible to everyone.
The Spring presents the @LoadBalancerClient
annotation to implement a client-side load balancer. There are two steps only.
Firstly, need to create the client-side load balancer:
public static class TestClientLoadBalancer {
private static final String SERVICE_ID = "session-service";
@Bean
public ServiceInstanceListSupplier staticServiceInstanceListSupplier() {
return ServiceInstanceListSuppliers.from(SERVICE_ID,
new DefaultServiceInstance(SERVICE_ID + "-1", SERVICE_ID, "localhost", LOAD_BALANCER_PORT, false));
}
}
Second step put it above the integration test:
@WireMockTest
@Testcontainers
@LoadBalancerClient(name = "test-client-side-load-balancer", configuration = TestClientLoadBalancer.class)
public abstract class IntegrationBaseTest {
Also, I had to get a port for the client load balancer and I got it from wiremock server.
public static int LOAD_BALANCER_PORT;
@BeforeAll
static void beforeAll() {
LOAD_BALANCER_PORT = WIREMOCK_SERVER.getPort();
}