Search code examples
javaspring-cloudnetflix-eurekawiremocktestcontainers

Spring Eureka in integration tests


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))
);

Solution

  • @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();
    }