How to Integration Test spring-cloud-netflix & Feign, using WireMock
Asked Answered
P

7

28

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 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 successfully inject such a WireMock server into Eureka, so that Feign is able to find it and communicate with it?

Pantheism answered 13/9, 2016 at 8:57 Comment(5)
I tried to offer an answer for you but I understand that most likely your goal is not really good. If you talk about integration tests, then you don't need to mock BarClient logic. if you do so then your test will be Unit test, not integration. And if it is a Unit test then you can mock BarClient simple with Mokito, without http requests at all. I don't understand why do you need http request?Kennykeno
I don't want to build integration tests that integrate multiple micro services. When I say integration test, I mean testing the integration of all technical layers in FooService, as opposed to unit tests that test only one class and replace the rest with mocks or stubs.Pantheism
Have you looked at RestClientTest and it's MockRestServiceServer in Spring Boot 1.4?Stimulant
Did you find a way to do this? I am trying to accomplish same. Running the microservice with all external dependencies (e.g. Eureka server) mocked out-of-process.Laguna
As you can see in my answer below, I switched to RestTemplate.Pantheism
P
-9

Use Spring's RestTemplate instead of feign. RestTemplate is also able to resolve service names via eureka, so you can do something like this:

@Component
public class SomeService {
   @Autowired
   RestTemplate restTemplate;

   public String doSomething() {
     try {
       restTemplate.postForEntity("http://my-service/some/url", 
                                  new BazzleRequest(...), 
                                  Void.class);
       return "so bazzle my eyes dazzle";
     } catch(HttpStatusCodeException e) {
       return "Not bazzle today!";
     }
   }
}

This is way easier testable with Wiremock than feign.

Pantheism answered 3/2, 2017 at 5:13 Comment(5)
But this doesn't use Feign, sure, it's valid if you use RestTemplate, but the question asks about Feign with SpringBoot. People use Feign because it's nicer than RestTemplate...Mathre
This does not answer the original question. You have completely changed your strategy meaning the original question is not applicable to you but it is still a valid question (and is exactly what I am am trying to do) and this is NOT the answer to it.Kerbstone
@Kerbstone Whatever. For me it solves the problem :)Pantheism
Really don't get it why everybody so hates my answer. This was my question, after all. And this is my solution which worked perfectly for me at the time. Anyway, I don't use spring-cloud at all any more because the whole thing is kind of crap and unstablePantheism
Perhaps if you just answered the question then people would upvote. By the way if you use discovery services then RestTemplate are a really bad solution because they aren't discovery aware out of the box.Levis
D
22

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.

Darrin answered 30/11, 2017 at 22:20 Comment(4)
Note that this approach relies on Hystrix being disabled by default otherwise the fallback might be used in live. Spring Cloud Dalston introduced the required opt-in approach but prior to that Hystrix would be enabled by default.Kerbstone
This should be the accepted answer with the proviso that it does not test the deserialization.Kerbstone
Worked perfectly for me. Couple of things to note: \nIf you are using JUnit 4.11 or higher, add this: @Rule public WireMockClassRule instanceRule = wiremock C
Also, if you are testing a Spring Boot 2 application, u want to depend on com.github.tomakehurst:wiremock-jre8:2.21.0C
G
7

Here is an example how to do the wiring of Feign and WireMock with random port (based on Spring-Boot github answer).

@RunWith(SpringRunner.class)
@SpringBootTest(properties = "google.url=http://google.com") // emulate application.properties
@ContextConfiguration(initializers = PortTest.RandomPortInitializer.class)
@EnableFeignClients(clients = PortTest.Google.class)
public class PortTest {

    @ClassRule
    public static WireMockClassRule wireMockRule = new WireMockClassRule(
        wireMockConfig().dynamicPort()
    );

    @FeignClient(name = "google", url = "${google.url}")
    public interface Google {    
        @RequestMapping(method = RequestMethod.GET, value = "/")
        String request();
    }

    @Autowired
    public Google google;

    @Test
    public void testName() throws Exception {
        stubFor(get(urlEqualTo("/"))
                .willReturn(aResponse()
                        .withStatus(HttpStatus.OK.value())
                        .withBody("Hello")));

        assertEquals("Hello", google.request());
    }


    public static class RandomPortInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
        @Override
        public void initialize(ConfigurableApplicationContext applicationContext) {

            // If the next statement is commented out, 
            // Feign will go to google.com instead of localhost
            TestPropertySourceUtils
                .addInlinedPropertiesToEnvironment(applicationContext,
                    "google.url=" + "http://localhost:" + wireMockRule.port()
            );
        }
    }
}

Alternatively you can try playing with System.setProperty() in @BeforeClass method of your test.

Glamour answered 11/8, 2017 at 20:21 Comment(3)
Hey, thanks for your answer! Does this involve Eureka? What is the google.url?Pantheism
Hello! 'google.url' is an arbitrary property name to hold the dependee's service url (google homepage in this example). Eureka is not taken into consideration. Came to this solution trying to override application.properties value.Glamour
TestPropertySourceUtils is great tip. Thank you. I wish I could find it in the official documentation somehow.Floruit
B
5

There used to be basically two options for doing integration tests for microservices applications:

  1. Deployment of services to a test environment and make end-to-end tests
  2. Mocking other microservices

First option has the obvious disadvantage of the hassle of deploying all the dependencies (other services, databases, etc) as well. In addition, it is slow and hard to debug.

Second option is faster and has less hassle but it is easy to end up with stubs that behave differently than the reality in time, due to possible code changes. So it is possible to have successful tests but failing app when deployed to prod.

A better solution would be using consumer driven contract verification, so that you will make sure that provider service's API is compliant with the consumer calls. For this purpose, Spring developers can use Spring Cloud Contract. For other environments, there is a framework called PACT. Both can be used with Feign clients as well. Here is an example with PACT.

Broke answered 1/12, 2017 at 11:33 Comment(0)
D
2

I personally prefer mockServer to stub any restful API, it is easy to use and is similar to wiremock, but is very powerful compared to the latter.

I have attached the sample code written with groovy/spock for stubbing a GET restful call with mockServer.

First autowire the mockServer instance in test class

@Autowired
private static ClientAndServer mockServer

start the mockServer instance from the setupSpec() method, this method is similar to junit method annotated with @BeforeClass.

def setupSpec() {
     mockServer = ClientAndServer.startClientAndServer(8080)
   }

define the required stub in the corresponding unit test

def "test case"() {
 given:
       new MockServerClient("localhost",8080).when(HttpRequest.request().withMethod("GET").withPath("/test/api").withQueryStringParameters(Parameter.param("param1", "param1_value"), Parameter.param("param2", "param2_value"))).respond(HttpResponse.response().withStatusCode(HttpStatus.OK.value()).withBody("{ message: 'sample response' }"))

 when:
 //your code
 then:
 //your code
}

after the execution of test cases, stop the mock server

def cleanupSpec() {
     mockServer.stop()
} 
Digger answered 4/2, 2020 at 9:33 Comment(0)
P
2

I think this is a very interesting yet underrated topic, how to verify your communication channels in a microservice environment. It's literally essential to make sure your channels are working as expected but still, I see tons of projects spending time on testing their Feign clients.

Most of the folks have already answered how to do minimal testing for your Feign clients but let's take it to the next level.

Testing the plain Feign client, the request mapping/response mapping/query mapping/etc is just one tiny part of the picture. In a microservice environment you gotta take care of service resiliency as well, like client-side load balancing, circuit breaking and so on.

Since it's 2021 and Spring Cloud marked Hystrix and Ribbon deprecated, it's time to look at Resilience4J.

I won't put the code here cause it would be probably too much to read but I'll give you some links to one of my GitHub projects.

Also, this might be a bit much too understand without further explanation yet I can't do it in a single stackoverflow answer, so you can check out a couple of my articles as well as my course on Feign: Mastering microservice communication with Spring Cloud Feign

Poetaster answered 4/12, 2021 at 16:39 Comment(0)
K
0

Probably there is no way to make WireMock comunicate directly with Eureka Server, but you can use other variants to configure test environment that you need.

  1. In test environment you can deploy Eureka Service Registry under standalone Jetty servlet container and all the annotations will work like they do in real production environment.
  2. If you don't want to use real BarClient endpoint logic, and integration test is only about real http transport layer, then you can use Mockito for BarClient endpoint stub.

I suppose that in order to implement 1 and 2 using Spring-Boot, you will need to make two separate applications for a test environment. One for Eureka Service Registry under Jetty and another for BarClient endpoint stub under Jetty too.

Another solution is to manually configure Jetty and Eureka in test application context. I think that this is a better way but in such case you must to understand what @EnableEurekaServer and @EnableDiscoveryClient annotations do with Spring application context.

Kennykeno answered 21/9, 2016 at 4:20 Comment(1)
Hey Sergey thanks for your answer! Adding a eureka registry to my service sounds good, but how can I add my fake BarService to it? Considering your second suggestion, replacing BarClient with a Mockito, yes I also want to do that, but that's for the unit test. I also want to have an integration test that involves the real feign magic.Pantheism
P
-9

Use Spring's RestTemplate instead of feign. RestTemplate is also able to resolve service names via eureka, so you can do something like this:

@Component
public class SomeService {
   @Autowired
   RestTemplate restTemplate;

   public String doSomething() {
     try {
       restTemplate.postForEntity("http://my-service/some/url", 
                                  new BazzleRequest(...), 
                                  Void.class);
       return "so bazzle my eyes dazzle";
     } catch(HttpStatusCodeException e) {
       return "Not bazzle today!";
     }
   }
}

This is way easier testable with Wiremock than feign.

Pantheism answered 3/2, 2017 at 5:13 Comment(5)
But this doesn't use Feign, sure, it's valid if you use RestTemplate, but the question asks about Feign with SpringBoot. People use Feign because it's nicer than RestTemplate...Mathre
This does not answer the original question. You have completely changed your strategy meaning the original question is not applicable to you but it is still a valid question (and is exactly what I am am trying to do) and this is NOT the answer to it.Kerbstone
@Kerbstone Whatever. For me it solves the problem :)Pantheism
Really don't get it why everybody so hates my answer. This was my question, after all. And this is my solution which worked perfectly for me at the time. Anyway, I don't use spring-cloud at all any more because the whole thing is kind of crap and unstablePantheism
Perhaps if you just answered the question then people would upvote. By the way if you use discovery services then RestTemplate are a really bad solution because they aren't discovery aware out of the box.Levis

© 2022 - 2024 — McMap. All rights reserved.