5

When writing an integration test for a Spring Boot application (Service A) that uses a RestTemplate (and Ribbon under the hood) and Eureka to resolve a Service B dependency, I get a "No instances available" exception when calling the Service A.

I try to mock the Service B away via WireMock, but I don't even get to the WireMock server. It seems like the RestTemplate tries to fetch the Service instance from Eureka, which doesn't run in my test. It is disabled via properties.

Service A calls Service B. Service discovery is done via RestTemplate, Ribbon and Eureka.

Does anyone have a working example that includes Spring, Eureka and WireMock?

derSteve
  • 751
  • 3
  • 8
  • 20
  • If you are okay with mocking service B, you can use `MockRestServiceServer`. I can share an example. – barbakini Sep 04 '17 at 20:04
  • That could be an option, but I would like to include the actual service call, even though the response is canned. – derSteve Sep 04 '17 at 20:33
  • When using `MockRestServiceServer` spring boot makes actually a call. but only that call it caught by the `MockRestServiceServer` and it returns the response you determined. And for the eureka part. It is already a well cooked frame work and you don't need to test it (for ribbon or load balance issues etc.) – barbakini Sep 04 '17 at 20:45
  • @barbakini Do you have an example to share? – derSteve Sep 04 '17 at 20:56
  • I post a complete example. But when I thought again, may be you only need to disable eureka for test. **eureka.client.enabled=false** – barbakini Sep 04 '17 at 21:21
  • This setting I also had from the beginning. It didn't solve the problem. – derSteve Sep 05 '17 at 09:54
  • May be because of wiremock you had issue from start. spring boot recommended testing scenario for RestTemplate is using `@RestClientTest` and `MockRestServiceServer ` which I shared in my answer. So no need to use a 3rd party mocking frameworks – barbakini Sep 05 '17 at 10:15

4 Answers4

3

I faced the same problem yesterday and for the sake of completeness here is my solution:

This is my "live" configuration under src/main/java/.../config:

//the regular configuration not active with test profile
@Configuration
@Profile("!test")
public class WebConfig {
    @LoadBalanced
    @Bean
    RestTemplate restTemplate() {
        //you can use your regular rest template here.
        //This one adds a X-TRACE-ID header from the MDC to the call.
        return TraceableRestTemplate.create();
    }
}

I added this configuration to the test folder src/main/test/java/.../config:

//the test configuration
@Configuration
@Profile("test")
public class WebConfig {
    @Bean
    RestTemplate restTemplate() {
        return TraceableRestTemplate.create();
    }
}

In the test case, I activated profile test:

 //...
@ActiveProfiles("test")
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ServerCallTest {
   @Autowired
   private IBusiness biz;

   @Autowired
   private RestTemplate restTemplate;

   private ClientHttpRequestFactory originalClientHttpRequestFactory;

   @Before
   public void setUp() {
       originalClientHttpRequestFactory = restTemplate.getRequestFactory();
   }

   @After
   public void tearDown() {
      restTemplate.setRequestFactory(originalClientHttpRequestFactory);
   }

   @Test
   public void fetchAllEntries() throws BookListException {
      MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);

       mockServer                
            .andExpect(method(HttpMethod.GET))
            .andExpect(header("Accept", "application/json"))
            .andExpect(requestTo(endsWith("/list/entries/")))
            .andRespond(withSuccess("your-payload-here", MediaType.APPLICATION_JSON));

       MyData data = biz.getData();

       //do your asserts
   }
}
Hannes
  • 2,018
  • 25
  • 32
0

This is what I done in my project:

Somewhere in your project configuration:

@LoadBalanced
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
    RestTemplate restTemplate = builder.build();
    restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
    return restTemplate;
}

@Bean
public SomeRestClass someRestClass () {
    SomeRestClass someRestClass = new SomeRestClass (environment.getProperty("someservice.uri"), restTemplate(new RestTemplateBuilder()));
    return parameterRest;
}

And SomeRestClass:

public class SomeRestClass {

    private final RestTemplate restTemplate;

    private final String someServiceUri;

    public LocationRest(String someServiceUri, RestTemplate restTemplate) {
        this.someServiceUri= someServiceUri;
        this.restTemplate = restTemplate;
    }

    public String getSomeServiceUri() {
        return locationUri;
    }

    public SomeObject getSomeObjectViaRest() {
        //making rest service call
    }
}

And Test class for SomeRestClass:

@RunWith(SpringRunner.class)
@RestClientTest(SomeRestClass.class)
public class SomeRestClassTest {
    @Autowired
    private SomeRestClass someRestClass;

    @Autowired
    private MockRestServiceServer server;

    @Test
    public void getSomeObjectViaRestTest() throws JsonProcessingException {
        SomeResponseObject resObject = new SomeResponseObject();
        ObjectMapper objectMapper = new ObjectMapper();
        String responseString = objectMapper.writeValueAsString(resObject);

        server.expect(requestTo(locationRest.getSomeServiceUri() + "/some-end-point?someParam=someParam")).andExpect(method(HttpMethod.POST))
            .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON_UTF8).body(responseString));
        someRestClass.getSomeObjectViaRest();

    }
}

Note: I diasbled eureka client because otherwise you have to mock a eureka server. So I added eureka.client.enabled=false in test application.properties

barbakini
  • 3,024
  • 2
  • 19
  • 25
  • Does the RestTemplate in this case realise that there isn't a ServiceRegistry/Eureka to ask for the specific service endpoint? In my test it doesn't realise this and tries to resolve a service from eureka. This is why I get the exception. I have the setting eureka.client.enabled=false set in my configuration -> which means it doesn't register itself with eureka as a service itself. – derSteve Sep 05 '17 at 09:54
  • @derSteve Yes, RestTemplate just try to hit address and MockRestServiceServer catches that request. The posted example is complete, I cut it from my project and I use eureka and RestTemplate. This test can give you what you want. One step further from this test is writing a **real** integration test via ruby-cucumber or any other QA related techs and run those tests in a test environment with actually up an running service A, service B and eureka service – barbakini Sep 05 '17 at 10:13
0

Hopefully this can help someone. I was getting the same error with Ribbon, but without Eureka.

What helped me was

1) Upgrading to the latest version of WireMock (2.21) in my case

2) Adding a wiremock rule stub for url "/" to answer Ribbon's ping

Gonen I
  • 5,576
  • 1
  • 29
  • 60
0

If you are using Eureka just bypass in test/application.properties using eureka.client.enabled=false

kanik
  • 381
  • 3
  • 6