/*
*
* Copyright 2017 Christopher Pilsworth
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
*/
package io.github.resilience4j.retrofit;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import okhttp3.OkHttpClient;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.scalars.ScalarsConverterFactory;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
import static com.github.tomakehurst.wiremock.client.WireMock.verify;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests the integration of the Retrofit HTTP client and {@link CircuitBreaker}
*/
public class RetrofitCircuitBreakerTest {
@Rule
public WireMockRule wireMockRule = new WireMockRule();
private static final CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
.ringBufferSizeInClosedState(3)
.waitDurationInOpenState(Duration.ofMillis(1000))
.build();
private CircuitBreaker circuitBreaker = CircuitBreaker.of("test", circuitBreakerConfig);
private RetrofitService service;
@Before
public void setUp() {
this.circuitBreaker = CircuitBreaker.of("test", circuitBreakerConfig);
final long TIMEOUT = 300; // ms
final OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
.readTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
.writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
.build();
this.service = new Retrofit.Builder()
.addCallAdapterFactory(CircuitBreakerCallAdapter.of(circuitBreaker))
.addConverterFactory(ScalarsConverterFactory.create())
.baseUrl("http://localhost:8080/")
.client(client)
.build()
.create(RetrofitService.class);
}
@Test
public void decorateSuccessfulCall() throws Exception {
stubFor(get(urlPathEqualTo("/greeting"))
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "text/plain")
.withBody("hello world")));
service.greeting().execute();
verify(1, getRequestedFor(urlPathEqualTo("/greeting")));
}
@Test
public void decorateTimingOutCall() throws Exception {
stubFor(get(urlPathEqualTo("/greeting"))
.willReturn(aResponse()
.withFixedDelay(500)
.withStatus(200)
.withHeader("Content-Type", "text/plain")
.withBody("hello world")));
try {
service.greeting().execute();
} catch (Throwable ignored) {
}
final CircuitBreaker.Metrics metrics = circuitBreaker.getMetrics();
assertThat(metrics.getNumberOfFailedCalls())
.describedAs("Failed calls")
.isEqualTo(1);
// Circuit breaker should still be closed, not hit open threshold
assertThat(circuitBreaker.getState())
.isEqualTo(CircuitBreaker.State.CLOSED);
try {
service.greeting().execute();
} catch (Throwable ignored) {
}
try {
service.greeting().execute();
} catch (Throwable ignored) {
}
assertThat(metrics.getNumberOfFailedCalls())
.isEqualTo(3);
// Circuit breaker should be OPEN, threshold met
assertThat(circuitBreaker.getState())
.isEqualTo(CircuitBreaker.State.OPEN);
}
@Test
public void decorateUnsuccessfulCall() throws Exception {
stubFor(get(urlPathEqualTo("/greeting"))
.willReturn(aResponse()
.withStatus(500)
.withHeader("Content-Type", "text/plain")));
final Response<String> response = service.greeting().execute();
assertThat(response.code())
.describedAs("Response code")
.isEqualTo(500);
final CircuitBreaker.Metrics metrics = circuitBreaker.getMetrics();
assertThat(metrics.getNumberOfFailedCalls()).isEqualTo(1);
}
@Test(expected = IllegalArgumentException.class)
public void shouldThrowOnBadService() {
BadRetrofitService badService = new Retrofit.Builder()
.addCallAdapterFactory(CircuitBreakerCallAdapter.of(circuitBreaker))
.baseUrl("http://localhost:8080/")
.build()
.create(BadRetrofitService.class);
badService.greeting();
}
}