web-dev-qa-db-ja.com

spring-cloud-netflixとfeignを使用した統合テストの書き方

マイクロサービス間の通信には、Spring-Cloud-Netflixを使用します。 FooとBarの2つのサービスがあり、FooはBarのRESTエンドポイントの1つを消費します。@FeignClient

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

次に、FooにSomeServiceを呼び出すサービスクラス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!";
      }

    }
}

ここで、サービス間の通信が機能することを確認するために、WireMockのようなものを使用して、偽のBarサーバーに対して実際のHTTP要求を実行するテストを作成します。このテストでは、feignがサービス応答を正しくデコードし、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!"));
    }
}

そのようなWireMockサーバーをeurekaにどのように挿入すれば、偽装がそれを見つけて通信できるようになりますか?どのようなアノテーションマジックが必要ですか?

21
Bastian Voigt

以下は、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();
    }
}

Feignクライアントにフォールバッククラスを指定していることに注意してください。フォールバッククラスは、Feignクライアントの呼び出しが失敗するたびに呼び出されます(接続タイムアウトなど)。

テストを機能させるには、リボンロードバランサーを構成する必要があります(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()));
        }
    }
}

リボンサーバーリストは、WireMock構成のURL(ホストとポート)と一致する必要があります。

15
mladzo

ランダムポートを使用してFeignとWireMockの配線を行う方法の例を次に示します( 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()
            );
        }
    }
}
_

代替 テストの_@BeforeClass_メソッドでSystem.setProperty()を試してみることができます。

6
Alexander

以前は、マイクロサービスアプリケーションの統合テストを行うための基本的に2つのオプションがありました。

  1. テスト環境へのサービスの展開とエンドツーエンドテストの実施
  2. 他のマイクロサービスのモック

最初のオプションには、すべての依存関係(他のサービス、データベースなど)もデプロイするという手間がかかるという明らかな欠点があります。さらに、それは遅く、デバッグが困難です。

2番目のオプションは高速で、手間が少ないですが、コード変更の可能性があるため、時間の現実とは異なる動作をするスタブになりやすいです。したがって、テストに成功しても、prodにデプロイしたときにアプリに失敗する可能性があります。

より良い解決策は、消費者主導の契約検証を使用することです。これにより、プロバイダーサービスのAPIが消費者呼び出しに準拠していることを確認できます。この目的のために、Spring開発者は Spring Cloud Contract を使用できます。他の環境には、 [〜#〜] pact [〜#〜] と呼ばれるフレームワークがあります。どちらもFeignクライアントで使用できます。 ここ はPACTの例です。

2
humbaba

おそらくWireMockをEureka Serverと直接通信させる方法はありませんが、他のバリアントを使用して必要なテスト環境を構成できます。

  1. テスト環境では、スタンドアロンのJettyサーブレットコンテナの下にEureka Service Registryをデプロイでき、すべての注釈は実際の実稼働環境でのように機能します。
  2. 実際のBarClientエンドポイントロジックを使用したくなく、統合テストが実際のhttpトランスポートレイヤーのみに関する場合、BarClientエンドポイントスタブにMockitoを使用できます。

Spring-Bootを使用して1と2を実装するには、テスト環境用に2つの個別のアプリケーションを作成する必要があると思います。 1つはJettyの下のEureka Service Registry用で、もう1つはJetty下のBarClientエンドポイントスタブ用です。

別の解決策は、テストアプリケーションコンテキストでJettyとEurekaを手動で構成することです。私はこれがより良い方法だと思いますが、そのような場合には、@EnableEurekaServerおよび@EnableDiscoveryClient注釈は、Springアプリケーションコンテキストで実行します。

0
Sergey Bespalov