Skip to content
This repository has been archived by the owner on Aug 13, 2022. It is now read-only.

Commit

Permalink
[#67] CircuitBreaker 테스트 코드 작성
Browse files Browse the repository at this point in the history
  • Loading branch information
yeoonjae committed Feb 7, 2022
1 parent 19c14de commit e7a682b
Show file tree
Hide file tree
Showing 12 changed files with 220 additions and 42 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ dependencies {
annotationProcessor 'org.projectlombok:lombok'
/* Junit, Hamcrest, Mockito 포함하는 스프링 어플리케이션을 테스트 가능하도록 합니다. */
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation("org.springframework.cloud:spring-cloud-starter-contract-stub-runner")
}

dependencyManagement {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import com.flab.doorrush.global.exception.KakaoApiResponseException;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand Down Expand Up @@ -73,12 +72,11 @@ public AddressInfoDTO getOriginAddress(Long addressSeq) {
.x(address.getSpotX().toString())
.y(address.getSpotY().toString())
.build();
return parseKakaoApiGetAddressResponse(kakaoAddressApi.getAddressBySpot(request));
return parseKakaoApiGetAddressResponse(kakaoAddressApi.getAddressBySpot(request).getBody());
}

private AddressInfoDTO parseKakaoApiGetAddressResponse(
ResponseEntity<KakaoApiGetAddressResponse> kakaoApiGetAddressResponse) {
KakaoApiGetAddressResponse response = kakaoApiGetAddressResponse.getBody();
KakaoApiGetAddressResponse response) {

if (!response.getMeta().isExist()) {
throw new KakaoApiResponseException("API 응답결과가 없습니다.");
Expand Down
38 changes: 21 additions & 17 deletions src/main/java/com/flab/doorrush/global/api/KakaoAddressApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,26 @@
@Slf4j
public class KakaoAddressApi {

public static final String KAKAO_HEADER = "KakaoAK ";
public static final String CUSTOM_CIRCUIT_BREAKER = "kakaoAddressApiCircuitBreaker";

private final RestTemplate restTemplate;

@Value("${api.authorization}")
@Value("${api.kakao.authorization}")
private String AUTHORIZATION;

public static final String KAKAO_HEADER = "KakaoAK ";
public static final String KAKAO_HOST = "https://dapi.kakao.com";
public static final String KAKAO_URL = "/v2/local/geo/coord2address.json";
public static final String CUSTOM_CIRCUIT_BREAKER = "customCircuitBreaker";
@Value("${api.kakao.host}")
public String KAKAO_HOST;

@Value("${api.kakao.url}")
public String KAKAO_URL;


/**
* @CircuitBreaker
* resilience4j Spring Boot2 스타터에서 제공되는 어노테이션으로 AOP 측면을 제공하는 역할입니다.
* @CircuitBreaker resilience4j Spring Boot2 스타터에서 제공되는 어노테이션으로 AOP 측면을 제공하는 역할입니다.
* CircuitBreaker라는 것을 명시하고 이름과 콜백 메소드를 지정할 수 있습니다.
*/
@CircuitBreaker(name = CUSTOM_CIRCUIT_BREAKER, fallbackMethod = "fallbackMethod")
*/
@CircuitBreaker(name = CUSTOM_CIRCUIT_BREAKER, fallbackMethod = "fallback")
public ResponseEntity<KakaoApiGetAddressResponse> getAddressBySpot(
KakaoApiGetAddressRequest getAddressRequest) {

Expand All @@ -62,16 +66,16 @@ public ResponseEntity<KakaoApiGetAddressResponse> getAddressBySpot(
return restTemplate.exchange(url, HttpMethod.GET, entity, KakaoApiGetAddressResponse.class);
}

private ResponseEntity<KakaoApiGetAddressResponse> fallbackMethod(
private ResponseEntity<KakaoApiGetAddressResponse> fallback(
KakaoApiGetAddressRequest getAddressRequest, Throwable t) {
log.info("KakaoAddressApi fallback Method running, Exception ={}", t.getMessage());

log.info("KakaoAddressApi fallback Method running, Exception = {}", t.getMessage());

List<GetAddressInfo> documents = new ArrayList<>();
new Meta("0");
KakaoApiGetAddressResponse response = KakaoApiGetAddressResponse.builder()
.meta(new Meta("0"))
.documents(documents)
.build();
return ResponseEntity.ok().body(response);
return ResponseEntity.ok().body(KakaoApiGetAddressResponse.builder()
.meta(Meta.builder().totalCount(0).build())
.documents(documents)
.build());
}

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.flab.doorrush.global.dto.response.kakao;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class Address {

@JsonProperty("address_name")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.flab.doorrush.global.dto.response.kakao;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class GetAddressInfo {

@JsonProperty("road_address")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
package com.flab.doorrush.global.dto.response.kakao;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.flab.doorrush.global.exception.KakaoApiResponseException;
import java.util.List;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@JsonIgnoreProperties({"mainAddress"})
public class KakaoApiGetAddressResponse {

private Meta meta;
private List<GetAddressInfo> documents;

public GetAddressInfo getMainAddress() {
if (documents.isEmpty()) {
throw new KakaoApiResponseException("응답값이 없습니다.");
}
return documents.get(0);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
package com.flab.doorrush.global.dto.response.kakao;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Meta {

@JsonProperty("total_count")
private String totalCount;
private int totalCount;

public boolean isExist() {
return !totalCount.equals("0");
return !(totalCount == 0);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.flab.doorrush.global.dto.response.kakao;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class RoadAddress {

@JsonProperty("address_name")
Expand Down
12 changes: 8 additions & 4 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ spring:
mybatis:
mapper-locations: classpath:mybatis/mapper/*Mapper.xml

# api
api:
authorization: ${AUTHORIZATION}
kakao:
authorization: ${AUTHORIZATION}
host: https://dapi.kakao.com
url: /v2/local/geo/coord2address.json

# resilience4j
resilience4j.circuitbreaker:
Expand All @@ -32,8 +36,8 @@ resilience4j.circuitbreaker:
maxWaitDurationInHalfOpenState: 1000
slidingWindowType: COUNT_BASED
slidingWindowSize: 10
minimumNumberOfCalls: 10
minimumNumberOfCalls: 5
waitDurationInOpenState: 10000
instances:
customCircuitBreaker:
baseConfig: default
kakaoAddressApiCircuitBreaker:
baseConfig: default
109 changes: 95 additions & 14 deletions src/test/java/com/flab/doorrush/global/api/KakaoAddressApiTest.java
Original file line number Diff line number Diff line change
@@ -1,35 +1,116 @@
package com.flab.doorrush.global.api;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static com.flab.doorrush.global.api.mock.KakaoMockObject.createKakaoApiFailResponse;
import static com.flab.doorrush.global.api.mock.KakaoMockObject.createKakaoApiRequest;
import static com.flab.doorrush.global.api.mock.KakaoMockObject.createKakaoApiSuccessResponse;
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.stubFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
import static org.junit.jupiter.api.Assertions.assertEquals;

import com.flab.doorrush.global.dto.request.KakaoApiGetAddressRequest;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.flab.doorrush.global.dto.response.kakao.KakaoApiGetAddressResponse;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreaker.State;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import java.util.Optional;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.cloud.contract.spec.internal.HttpStatus;
import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.ActiveProfiles;


@SpringBootTest
@ActiveProfiles("test")
@SpringBootTest(webEnvironment = WebEnvironment.NONE)
@AutoConfigureWireMock(port = 0)
class KakaoAddressApiTest {


@Autowired
KakaoAddressApi kakaoAddressApi;

@Autowired
ObjectMapper objectMapper;

@Autowired
CircuitBreakerRegistry CircuitBreakerRegistry;

@Test
@DisplayName("좌표로 주소 받아오는 API 성공 테스트")
public void getAddressSuccessTest() {
ResponseEntity<KakaoApiGetAddressResponse> result = kakaoAddressApi.getAddressBySpot(KakaoApiGetAddressRequest.builder()
.x("127.423084873712")
.y("37.0789561558879")
.build());

KakaoApiGetAddressResponse response = result.getBody();
assertThat(response.getMainAddress().getRoadAddress().getAddressName()).isEqualTo("경기도 안성시 죽산면 죽산초교길 69-4");
assertThat(response.getMainAddress().getAddress().getAddressName()).isEqualTo("경기 안성시 죽산면 죽산리 343-1");
assertTrue(response.getMeta().isExist());
public void getAddressSuccessTest() throws JsonProcessingException {

// Given
stubFor(get(urlPathEqualTo("/v2/local/geo/coord2address.json"))
.willReturn(aResponse()
.withStatus(200)
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.withBody(objectMapper.writeValueAsString(createKakaoApiSuccessResponse()))));

// When
ResponseEntity<KakaoApiGetAddressResponse> response = kakaoAddressApi.getAddressBySpot(
createKakaoApiRequest());

// Then
assertEquals(objectMapper.writeValueAsString(response.getBody()),
objectMapper.writeValueAsString(createKakaoApiSuccessResponse()));
assertEquals(HttpStatus.OK, response.getStatusCode().value());
}

@Test
@DisplayName("좌표로 주소 받아오는 API 실패 테스트 - fallback 메소드 작동")
public void getAddressRunningFallbackTest() throws JsonProcessingException {

// Given
stubFor(get(urlPathEqualTo("/v2/local/geo/coord2address.json"))
.willReturn(aResponse()
.withStatus(500)
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.withBody(objectMapper.writeValueAsString(createKakaoApiFailResponse()))));

// When
ResponseEntity<KakaoApiGetAddressResponse> response = kakaoAddressApi.getAddressBySpot(
createKakaoApiRequest());

// Then
assertEquals(objectMapper.writeValueAsString(response.getBody()),
objectMapper.writeValueAsString(createKakaoApiFailResponse()));
assertEquals(HttpStatus.OK, response.getStatusCode().value());
}

@Test
@DisplayName("minimumNumberOfCalls에 도달 시 CircuitBreaker OPEN 상태로 변경")
public void shouldCircuitBreakerOpenStatus() throws JsonProcessingException {
// Given
Optional<CircuitBreaker> circuitBreaker = CircuitBreakerRegistry.find(
"kakaoAddressApiCircuitBreaker");
int minimumNumberOfCalls = circuitBreaker.get().getCircuitBreakerConfig()
.getMinimumNumberOfCalls();

stubFor(get(urlPathEqualTo("/v2/local/geo/coord2address.json"))
.willReturn(aResponse()
.withStatus(500)
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.withBody(objectMapper.writeValueAsString(createKakaoApiFailResponse()))));

circuitBreaker.get().reset();
// When
for (int i = 1; i < minimumNumberOfCalls + 10; i++) {
kakaoAddressApi.getAddressBySpot(createKakaoApiRequest());
// Then
if (i < minimumNumberOfCalls) {
assertEquals(State.CLOSED, circuitBreaker.get().getState());
} else {
assertEquals(State.OPEN, circuitBreaker.get().getState());
}
}
}
}
Loading

0 comments on commit e7a682b

Please sign in to comment.