- java - MockRestServiceServer simulate backend timeout in integration test - Stack Overflow
- java - How to simulate timeout in response to a Rest request in Spring? - Stack Overflow
- java - How to tell a Mockito mock object to return something different the next time it is called? - Stack Overflow
- Mockito - mockito-core 4.0.0 javadoc
「RestTemplate で外部サーバにアクセスするときに確率的にタイムアウトする」という状況を単体テストで実現したいとします。外部サーバにアクセスするときのテストでは MockRestServiceServer で外部サーバをモックするのでこれを利用するのだろうなあと思って検索すると参考文献 1. にヒットするのでそのベストアンサーを参考に以下のように実装します。しかし、常に失敗すべきこのテストは一向に失敗しないことがわかります。ベストアンサーの回答者が "But, I should warn you," といっているように、MockRestServiceServer を RestTemplate にバインドしたことでもはや元々の RestTemplate の RequestFactory がタイムアウト値ごと置換されていることがわかります。となると質問に対するベストアンサーではないような気がしてならないですが放っておくことにします。
package com.example.bentou; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.http.HttpStatus; import org.springframework.mock.http.client.MockClientHttpResponse; import org.springframework.test.web.client.MockRestServiceServer; import org.springframework.web.client.RestTemplate; import java.time.Duration; import java.util.Random; import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.*; import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; class BentouApplicationTest { // RestTemplate を用意 (タイムアウト2秒) private RestTemplate restTemplate = new RestTemplateBuilder() .setConnectTimeout(Duration.ofSeconds(1)) .setReadTimeout(Duration.ofSeconds(1)) .build(); // ランダムスリープする関数 (失敗させるために長めにスリープ) private Random rand = new Random(); public void randomSleep() { int duration = 5 + rand.nextInt(5); try { TimeUnit.SECONDS.sleep(duration); } catch (InterruptedException ignored) {} } // RestTemplate から /abc にアクセスするとランダムスリープしてから "OK" を返すようにモック @BeforeEach public void setUp() { MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build(); mockServer.expect(requestTo("/abc")).andRespond(request -> { randomSleep(); return new MockClientHttpResponse("OK".getBytes(), HttpStatus.OK); }); } // RestTemplate から /abc にアクセス @Test public void test() { String resp = restTemplate.getForObject("abc", String.class); assertEquals("OK", resp); } }
なので MockRestServiceServer を忘れて参考文献 2.〜4. を参考に以下のように実装すると4回目の外部アクセスで失敗するという挙動は実現できます。いい方法かわかりません。これはモックなので RestTemplate 自体のタイムアウトのテストにはなっていないですが、RestTemplate による外部アクセスをサーキットブレーカーでラップしたときにサーキットブレーカーが意図通りの挙動をするかのテストには利用できると思います。というか RestTemplate をわざとタイムアウトさせてサーキットブレーカーの挙動を勉強しようとしたのですがそもそも RestTemplate をタイムアウトさせるのに躓いたという話でした。
package com.example.bentou; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class BentouApplicationTest { // RestTemplate を用意 (モック) @Mock private RestTemplate restTemplate; // RestTemplate から /abc にアクセスすると4回目に失敗するようにモック @BeforeEach public void setUp() { when(restTemplate.getForObject("abc", String.class)) .thenReturn("OK", "OK", "OK") .thenThrow(new RestClientException("ERROR")); } // RestTemplate から /abc にアクセス @Test public void test() { String resp; // 最初の3回は成功 for (int i = 0; i < 3; ++i) { resp = restTemplate.getForObject("abc", String.class); assertEquals("OK", resp); } // 4回目は失敗 try { restTemplate.getForObject("abc", String.class); fail(); } catch (RestClientException exception) { } } }