マイクロサービス間の通信には、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にどのように挿入すれば、偽装がそれを見つけて通信できるようになりますか?どのようなアノテーションマジックが必要ですか?
以下は、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(ホストとポート)と一致する必要があります。
ランダムポートを使用して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()
を試してみることができます。
以前は、マイクロサービスアプリケーションの統合テストを行うための基本的に2つのオプションがありました。
最初のオプションには、すべての依存関係(他のサービス、データベースなど)もデプロイするという手間がかかるという明らかな欠点があります。さらに、それは遅く、デバッグが困難です。
2番目のオプションは高速で、手間が少ないですが、コード変更の可能性があるため、時間の現実とは異なる動作をするスタブになりやすいです。したがって、テストに成功しても、prodにデプロイしたときにアプリに失敗する可能性があります。
より良い解決策は、消費者主導の契約検証を使用することです。これにより、プロバイダーサービスのAPIが消費者呼び出しに準拠していることを確認できます。この目的のために、Spring開発者は Spring Cloud Contract を使用できます。他の環境には、 [〜#〜] pact [〜#〜] と呼ばれるフレームワークがあります。どちらもFeignクライアントで使用できます。 ここ はPACTの例です。
おそらくWireMockをEureka Serverと直接通信させる方法はありませんが、他のバリアントを使用して必要なテスト環境を構成できます。
BarClient
エンドポイントロジックを使用したくなく、統合テストが実際のhttp
トランスポートレイヤーのみに関する場合、BarClient
エンドポイントスタブにMockitoを使用できます。Spring-Bootを使用して1と2を実装するには、テスト環境用に2つの個別のアプリケーションを作成する必要があると思います。 1つはJettyの下のEureka Service Registry用で、もう1つはJetty下のBarClient
エンドポイントスタブ用です。
別の解決策は、テストアプリケーションコンテキストでJettyとEurekaを手動で構成することです。私はこれがより良い方法だと思いますが、そのような場合には、@EnableEurekaServer
および@EnableDiscoveryClient
注釈は、Springアプリケーションコンテキストで実行します。