如何使用spring-cloud-netflix和feign编写集成测试

Bas*_*igt 21 java wiremock netflix-eureka feign spring-cloud-netflix

我使用Spring-Cloud-Netflix进行微服务之间的通信.假设我有两个服务,Foo和Bar,而Foo使用Bar的REST端点之一.我使用一个带注释的接口@FeignClient:

@FeignClient
public interface BarClient {
  @RequestMapping(value = "/some/url", method = "POST")
  void bazzle(@RequestBody BazzleRequest);
}
Run Code Online (Sandbox Code Playgroud)

然后我SomeService在Foo中有一个服务类,它调用了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!";
      }

    }
}
Run Code Online (Sandbox Code Playgroud)

现在,为了确保服务之间的通信正常工作,我想构建一个测试,使用WireMock之类的东西,对一个假的Bar服务器发出真正的HTTP请求.测试应确保假设正确解码服务响应并报告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!"));
    }
}
Run Code Online (Sandbox Code Playgroud)

如何将这样的WireMock服务器注入eureka,以便假装能够找到它并与之通信?我需要什么样的注释魔法?

mla*_*dzo 16

下面是使用WireMock通过Feign客户端和Hystrix回退测试SpringBoot配置的示例.

如果您使用Eureka作为服务器发现,则需要通过设置属性来禁用它"eureka.client.enabled=false".

首先,我们需要为我们的应用程序启用Feign/Hystrix配置:

@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();
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,我们正在为Feign客户端指定一个回退类.每当Feign客户端调用失败(例如连接超时)时,都会调用Fallback类.

为了使测试工作,我们需要配置Ribbon负载均衡器(在发送http请求时将由Feign客户端在内部使用):

@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()));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

功能区服务器列表需要匹配WireMock配置的URL(主机和端口).


Ale*_*der 6

下面是一个如何使用随机端口连接Feign和WireMock的示例(基于Spring-Boot github答案).

@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()
            );
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

或者你可以尝试打System.setProperty()@BeforeClass你的测试方法.


小智 5

过去,对微服务应用程序进行集成测试基本上有两种选择:

  1. 将服务部署到测试环境并进行端到端测试
  2. 模拟其他微服务

第一个选项的明显缺点是部署所有依赖项(其他服务、数据库等)也很麻烦。此外,它速度慢且难以调试。

第二种选择速度更快且麻烦更少,但由于可能的代码更改,很容易最终产生与实际行为不同的存根。因此,在部署到 prod 时,可能会有成功的测试但失败的应用程序。

更好的解决方案是使用消费者驱动的合同验证,以便您确保提供者服务的 API 与消费者调用兼容。为此,Spring 开发人员可以使用Spring Cloud Contract。对于其他环境,有一个名为PACT的框架。两者都可以与 Feign 客户端一起使用。是 PACT 的示例。


Bas*_*igt -9

使用Spring的RestTemplate代替feign。RestTemplate 还能够通过 eureka 解析服务名称,因此您可以执行以下操作:

@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!";
     }
   }
}
Run Code Online (Sandbox Code Playgroud)

使用 Wiremock 比 feign 更容易测试。

  • 这并没有回答原来的问题。您已经完全改变了您的策略,这意味着原来的问题不适用于您,但它仍然是一个有效的问题(并且正是我正在尝试做的),这不是它的答案。 (4认同)
  • 但这并没有使用Feign,当然,如果使用RestTemplate也是有效的,但是问题问的是Feign与SpringBoot的关系。人们使用 Feign 因为它比 RestTemplate 更好...... (2认同)