I use Spring-Cloud-Netflix for communication between micro services. Let's say I have two services, Foo and Bar, and Foo consumes one of Bar's REST endpoints. I use an interface annotated with @FeignClient
:
@FeignClient public interface BarClient { @RequestMapping(value = "/some/url", method = "POST") void bazzle(@RequestBody BazzleRequest); }
Then I have a service class SomeService
in Foo, which calls the BarClient
.
@Component public class SomeService { @Autowired BarClient barClient; public String doSomething() { try { barClient.bazzle(new BazzleRequest(...)); return "so bazzle my eyes dazzle"; } catch(FeignException e) { return "Not bazzle today!"; } } }
Now, to make sure the communication between services works, I want to build a test that fires a real HTTP request against a fake Bar server, using something like WireMock. The test should make sure that feign correctly decodes the service response and reports it to SomeService
.
public class SomeServiceIntegrationTest { @Autowired SomeService someService; @Test public void shouldSucceed() { stubFor(get(urlEqualTo("/some/url")) .willReturn(aResponse() .withStatus(204); String result = someService.doSomething(); assertThat(result, is("so bazzle my eyes dazzle")); } @Test public void shouldFail() { stubFor(get(urlEqualTo("/some/url")) .willReturn(aResponse() .withStatus(404); String result = someService.doSomething(); assertThat(result, is("Not bazzle today!")); } }
How can I inject such a WireMock server into eureka, so that feign is able to find it and communicate with it? What kind of annotation magic do I need?
The Spring Framework provides first-class support for integration testing in the spring-test module. The name of the actual JAR file might include the release version and might also be in the long org. springframework.
Here is an example of using WireMock to test SpringBoot configuration with Feign client and Hystrix fallback.
If you are using Eureka as a server discovery, you need to disable it by setting a property "eureka.client.enabled=false"
.
First, we need to enable the Feign/Hystrix configuration for our application:
@SpringBootApplication @EnableFeignClients @EnableCircuitBreaker public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } @FeignClient( name = "bookstore-server", fallback = BookClientFallback.class, qualifier = "bookClient" ) public interface BookClient { @RequestMapping(method = RequestMethod.GET, path = "/book/{id}") Book findById(@PathVariable("id") String id); } @Component public class BookClientFallback implements BookClient { @Override public Book findById(String id) { return Book.builder().id("fallback-id").title("default").isbn("default").build(); } }
Please note that we are specifying a fallback class for the Feign client. Fallback class will be called every time Feign client call fails (e.g. connection timeout).
In order for tests to work, we need to configure the Ribbon loadbalancer (will be used internally by Feign client when sending http request):
@RunWith(SpringRunner.class) @SpringBootTest(properties = { "feign.hystrix.enabled=true" }) @ContextConfiguration(classes = {BookClientTest.LocalRibbonClientConfiguration.class}) public class BookClientTest { @Autowired public BookClient bookClient; @ClassRule public static WireMockClassRule wiremock = new WireMockClassRule( wireMockConfig().dynamicPort())); @Before public void setup() throws IOException { stubFor(get(urlEqualTo("/book/12345")) .willReturn(aResponse() .withStatus(HttpStatus.OK.value()) .withHeader("Content-Type", MediaType.APPLICATION_JSON) .withBody(StreamUtils.copyToString(getClass().getClassLoader().getResourceAsStream("fixtures/book.json"), Charset.defaultCharset())))); } @Test public void testFindById() { Book result = bookClient.findById("12345"); assertNotNull("should not be null", result); assertThat(result.getId(), is("12345")); } @Test public void testFindByIdFallback() { stubFor(get(urlEqualTo("/book/12345")) .willReturn(aResponse().withFixedDelay(60000))); Book result = bookClient.findById("12345"); assertNotNull("should not be null", result); assertThat(result.getId(), is("fallback-id")); } @TestConfiguration public static class LocalRibbonClientConfiguration { @Bean public ServerList<Server> ribbonServerList() { return new StaticServerList<>(new Server("localhost", wiremock.port())); } } }
Ribbon server list need to match the url (host and port) of our WireMock configuration.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With