12

When working with Spring Boot to build micro-services its very easy to write extensive and very readable integration tests and mock remote service requests with MockRestServiceServer.

Is there a way to use similar approach to perform additional integration test on ZuulProxy? What I would like to achieve is being able to mock remote servers that ZuulProxy would forward to and validate that all of my ZuulFitlers behaved as expected. However, ZuulProxy is using RestClient from Netflix (deprecated it would seem?) which naturally does not use RestTemplate which could be re-configured by MockRestServiceServer and I currently can't find a good way of mocking responses from remote services for proxied requests.

I have a micro-service that is responsible for handling API Session Key creation and then will act similar to an API Gateway. Forwarding is done with Zuul Proxy to underlying exposed services, and Zuul Filters will detect if Session key is valid or not. An integration test would therefore create a valid session and then forward to a fake endpoint, e.g 'integration/test'.

Specifying that 'integration/test' is a new endpoint is possible by setting a configuration property on @WebIntegrationTest, I can successfully mock all services that are being handled via RestTemplate but not Zuul forwarding.

What's the best way to do achieve mocking of a forward target service?

Alexej Kubarev
  • 853
  • 8
  • 14

2 Answers2

8

Check out WireMock. I have been using it to do integration level testing of my Spring Cloud Zuul project.

import static com.github.tomakehurst.wiremock.client.WireMock.*;

public class TestClass {
    @Rule
    public WireMockRule serviceA = new WireMockRule(WireMockConfiguration.options().dynamicPort());

    @Before
    public void before() {
        serviceA.stubFor(get(urlPathEqualTo("/test-path/test")).willReturn(aResponse()
            .withHeader("Content-Type", "application/json").withStatus(200).withBody("serviceA:test-path")));
    }

    @Test
    public void testRoute() {
        ResponseEntity<String> responseEntity = this.restTemplate.getForEntity("/test-path/test", String.class);
        assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);

        serviceA.verify(1, getRequestedFor(urlPathEqualTo("/test-path/test")));
    }
}
Impurity
  • 1,037
  • 2
  • 16
  • 31
Shawn Clark
  • 3,330
  • 2
  • 18
  • 30
  • 3
    is there any configuration that needs to be added for this to work? I'm trying to do something similar but Zuul doesn't pick the route up. I get this exception `Caused by: com.netflix.client.ClientException: Load balancer does not have available server for client: strategie`. I imagine that comes from the Zuul configuration expecting information from the service discovery server, in this case Eureka, to route the call to the right ip. – Juan Vega Jul 27 '17 at 16:56
2

The accepted answer has the main idea. But I struggle on some points until figure out the problem. So I would like to show a more complete answer using also Wiremock.

The test:

@ActiveProfiles("test")
@TestPropertySource(locations = "classpath:/application-test.yml")
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWireMock(port = 5001)
public class ZuulRoutesTest {

    @LocalServerPort
    private int port;

    private TestRestTemplate restTemplate = new TestRestTemplate();

    @Before
    public void before() {

        stubFor(get(urlPathEqualTo("/1/orders/")).willReturn(aResponse()
                .withHeader("Content-Type", MediaType.TEXT_HTML_VALUE)
                .withStatus(HttpStatus.OK.value())));
    }

    @Test
    public void urlOrders() {
        ResponseEntity<String> result = this.restTemplate.getForEntity("http://localhost:"+this.port +"/api/orders/", String.class);
        assertEquals(HttpStatus.OK, result.getStatusCode());

        verify(1, getRequestedFor(urlPathMatching("/1/.*")));
    }
}

And the application-test.yml:

zuul:
  prefix: /api
  routes:
    orders:
      url: http://localhost:5001/1/
    cards:
      url: http://localhost:5001/2/

This should work.

But Wiremock has some limitations for me. If you has proxy requests with different hostnames running on different ports, like this:

zuul:
  prefix: /api
  routes:
    orders:
      url: http://lp-order-service:5001/
    cards:
      url: http://lp-card-service:5002/

A localhost Wiremock running on the same port will no be able to help you. I'm still trying to find a similar Integration Test where I could just mock a Bean from Spring and read what url the Zuul Proxy choose to route before it make the request call.

Dherik
  • 17,757
  • 11
  • 115
  • 164
  • I think the whole idea of configuration system is to be able to setup overrides for "production" config and during test point it to the localhost mocks instead of different domains. Otherwise you are looking into more of an end-to-end tests. Therefore, I wouldn't say it's a Wiremock limitation but rather a setup that might need a different approach? As an example, you might want to use a different profile for integration tests that go towards the mock server – Alexej Kubarev Aug 25 '19 at 21:00
  • @Dherik isn't it what you're looking for? Just define two `WireMockRule`s and stub them separately http://wiremock.org/docs/junit-rule/ – VB_ Dec 03 '19 at 22:06
  • @Dherik you should be able to get around that limitation by using the url as `forward://service-test-A` just for testing in application-test.yml – bhantol May 19 '21 at 18:55