99import static org .assertj .core .api .Assertions .assertThatThrownBy ;
1010import static org .mockito .ArgumentMatchers .any ;
1111import static org .mockito .ArgumentMatchers .anyLong ;
12+ import static org .mockito .Mockito .doAnswer ;
1213import static org .mockito .Mockito .doNothing ;
13- import static org .mockito .Mockito .doThrow ;
14+ import static org .mockito .Mockito .mock ;
1415import static org .mockito .Mockito .never ;
1516import static org .mockito .Mockito .spy ;
1617import static org .mockito .Mockito .verify ;
3233import java .time .Duration ;
3334import java .util .concurrent .TimeUnit ;
3435import java .util .function .Predicate ;
36+ import java .util .function .Supplier ;
3537import java .util .logging .Level ;
3638import java .util .logging .Logger ;
3739import java .util .stream .Stream ;
40+ import okhttp3 .Interceptor ;
3841import okhttp3 .OkHttpClient ;
3942import okhttp3 .Request ;
4043import okhttp3 .Response ;
4750import org .junit .jupiter .params .provider .MethodSource ;
4851import org .junit .jupiter .params .provider .ValueSource ;
4952import org .mockito .Mock ;
53+ import org .mockito .invocation .InvocationOnMock ;
5054import org .mockito .junit .jupiter .MockitoExtension ;
55+ import org .mockito .stubbing .Answer ;
5156
5257@ ExtendWith (MockitoExtension .class )
5358class RetryInterceptorTest {
5459
5560 @ RegisterExtension static final MockWebServerExtension server = new MockWebServerExtension ();
5661
5762 @ Mock private RetryInterceptor .Sleeper sleeper ;
58- @ Mock private RetryInterceptor . BoundedLongGenerator random ;
63+ @ Mock private Supplier < Double > random ;
5964 private Predicate <IOException > retryExceptionPredicate ;
6065
6166 private RetryInterceptor retrier ;
@@ -91,6 +96,24 @@ public boolean test(IOException e) {
9196 client = new OkHttpClient .Builder ().addInterceptor (retrier ).build ();
9297 }
9398
99+ @ Test
100+ void noRetryOnNullResponse () throws IOException {
101+ Interceptor .Chain chain = mock (Interceptor .Chain .class );
102+ when (chain .proceed (any ())).thenReturn (null );
103+ when (chain .request ())
104+ .thenReturn (new Request .Builder ().url (server .httpUri ().toString ()).build ());
105+ assertThatThrownBy (
106+ () -> {
107+ retrier .intercept (chain );
108+ })
109+ .isInstanceOf (NullPointerException .class )
110+ .hasMessage ("response cannot be null." );
111+
112+ verifyNoInteractions (retryExceptionPredicate );
113+ verifyNoInteractions (random );
114+ verifyNoInteractions (sleeper );
115+ }
116+
94117 @ Test
95118 void noRetry () throws Exception {
96119 server .enqueue (HttpResponse .of (HttpStatus .OK ));
@@ -109,17 +132,8 @@ void noRetry() throws Exception {
109132 @ ValueSource (ints = {5 , 6 })
110133 void backsOff (int attempts ) throws Exception {
111134 succeedOnAttempt (attempts );
112-
113- // Will backoff 4 times
114- when (random .get ((long ) (TimeUnit .SECONDS .toNanos (1 ) * Math .pow (1.6 , 0 )))).thenReturn (100L );
115- when (random .get ((long ) (TimeUnit .SECONDS .toNanos (1 ) * Math .pow (1.6 , 1 )))).thenReturn (50L );
116- // Capped
117- when (random .get (TimeUnit .SECONDS .toNanos (2 ))).thenReturn (500L ).thenReturn (510L );
118-
119- doNothing ().when (sleeper ).sleep (100 );
120- doNothing ().when (sleeper ).sleep (50 );
121- doNothing ().when (sleeper ).sleep (500 );
122- doNothing ().when (sleeper ).sleep (510 );
135+ when (random .get ()).thenReturn (1.0d );
136+ doNothing ().when (sleeper ).sleep (anyLong ());
123137
124138 try (Response response = sendRequest ()) {
125139 if (attempts <= 5 ) {
@@ -139,16 +153,26 @@ void interrupted() throws Exception {
139153 succeedOnAttempt (5 );
140154
141155 // Backs off twice, second is interrupted
142- when (random .get ((long ) (TimeUnit .SECONDS .toNanos (1 ) * Math .pow (1.6 , 0 )))).thenReturn (100L );
143- when (random .get ((long ) (TimeUnit .SECONDS .toNanos (1 ) * Math .pow (1.6 , 1 )))).thenReturn (50L );
156+ when (random .get ()).thenReturn (1.0d ).thenReturn (1.0d );
157+ doAnswer (
158+ new Answer <Void >() {
159+ int counter = 0 ;
144160
145- doNothing ().when (sleeper ).sleep (100 );
146- doThrow (new InterruptedException ()).when (sleeper ).sleep (50 );
161+ @ Override
162+ public Void answer (InvocationOnMock invocation ) throws Throwable {
163+ if (counter ++ == 1 ) {
164+ throw new InterruptedException ();
165+ }
166+ return null ;
167+ }
168+ })
169+ .when (sleeper )
170+ .sleep (anyLong ());
147171
148172 try (Response response = sendRequest ()) {
149173 assertThat (response .isSuccessful ()).isFalse ();
150174 }
151-
175+ verify ( sleeper , times ( 2 )). sleep ( anyLong ());
152176 for (int i = 0 ; i < 2 ; i ++) {
153177 server .takeRequest (0 , TimeUnit .NANOSECONDS );
154178 }
@@ -157,7 +181,7 @@ void interrupted() throws Exception {
157181 @ Test
158182 void connectTimeout () throws Exception {
159183 client = connectTimeoutClient ();
160- when (random .get (anyLong ())) .thenReturn (1L );
184+ when (random .get ()) .thenReturn (1.0d );
161185 doNothing ().when (sleeper ).sleep (anyLong ());
162186
163187 // Connecting to a non-routable IP address to trigger connection error
@@ -174,7 +198,7 @@ void connectTimeout() throws Exception {
174198 @ Test
175199 void connectException () throws Exception {
176200 client = connectTimeoutClient ();
177- when (random .get (anyLong ())) .thenReturn (1L );
201+ when (random .get ()) .thenReturn (1.0d );
178202 doNothing ().when (sleeper ).sleep (anyLong ());
179203
180204 // Connecting to localhost on an unused port address to trigger java.net.ConnectException
0 commit comments