두둥 서비스에서 api client로 feign을 사용중이다.
매우편하게 인터페이스로 선언만 해놓으면, 응답받는거와 비슷한 형식으로 사용가능하다.
또한 인터페이스 이므로 테스트 과정에서 mocking 해야할때도 편하게 진행할수있다.
토스페이먼츠에는 발생했던 매출에서 수수료를 뺀 금액 즉 정산 받을 금액을 조회할려면,
정산 조회 작업을 거쳐서 발생 주문 건에 대해서 PG 수수료를 계산해야한다.
이전까진 테스트키로 개발을 하다가, 실제 결제가 이루어지기 전까진 정산데이타가 넘어오지 않아서
응답 dto로 알맞게 파싱되는지, 테스트를 해보고 싶었다.
파싱이 제대로 되는지,
테스트를 진행하기 위해서는 정말 요청을 보낼수 있는 환경을 구성해서
요청을 보낸뒤에 그에 맞는 응답을 내려주는 형식으로 구성을 해야한다.
그방법을 공유하고자 한다.
목차
1. wiremock
2. 적용하기
2.1. feign client
2.2. 테스트 코드 작성하기
1. wiremock
wiremock 은 테스트 목적으로 사용할 수 있는 웹 서버이다. 즉 테스트 환경에서 요청이 들어오면 정해진 응답을 반환 할 수 있다.
wiremock 자체로도 서버를 껏다 킬 수 있지만 스프링과도 연동이 가능하다.
testImplementation "org.springframework.cloud:spring-cloud-contract-wiremock:3.1.5"
위 모듈을 추가하게 되면 스프링과 쉽게 연동할 수 있다. ( 공식문서에 있는 내용 )
우린 standalone 모드 대신에 테스트 과정에 포함시킬거다.
[
{
"mId": "tosspayments",
"paymentKey": "5zJ4xY7m0kODnyRpQWGrN2xqGlNvLrKwv1M9ENjbeoPaZdL6",
"transactionKey": "8B4F646A829571D870A3011A4E13D640",
"orderId": "a4CWyWY5m89PNh7xJwhk1",
"currency": "KRW",
"method": "카드",
"amount": 34000
}
]
간단하게 json 파일을 위와 같이 만들어두면,
Path file = ResourceUtils.getFile("classpath:payload/settlement-response.json").toPath();
// 응답예시 생성하기
.withBody(Files.readAllBytes(file))));
테스트 코드 작성시에 미리 만들어둔 json 응답을 줄 수 있다.
2. 적용하기
2.1. feign client
@FeignClient(
name = "SettlementClient",
// url 자체를 환경변수로 세팅한다.
url = "${feign.toss.url}",
configuration = {TransactionGetConfig.class})
public interface SettlementClient {
@GetMapping("/v1/settlements")
List<SettlementResponse> execute(
@RequestParam(value = "startDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
LocalDate startDate,
@RequestParam(value = "endDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
LocalDate endDate,
@RequestParam(value = "dateType") String dateType,
@RequestParam(value = "page") int page,
@RequestParam(value = "size") int size);
}
// application-infrastructure.yml
feign:
toss:
url : https://api.tosspayments.com
일반적인 요청인데 중요한 부분은 url 자체를 환경변수로 넣어야한다는 점이다.
실제 요청 주소를 default 로 세팅 해놓고 테스트를 돌릴 때 @TestPropertySource 로 환경변수를 localhost로 지정줘서
요청을 보낼 것이다.
여담이지만 @TestPropertySource 는 엄청 유용하게 쓸데가 많은것 같다.
내가 쓴글인 spring disable aop in test 도
테스트 돌 때 어느 한 aop 를 끄고싶을 때도 설정할 수 있다.!
위글에서는 분산락을 aop 통해서 적용하고 있었는데,
분산락 지정을 안했을때의 실패테스트를 하고 싶을 때, 간단하게 환경변수로 분산락 적용을 없앨 수 있다.
유용하니 한번 둘러보길 바란다 ㅎㅎ.
이 외엔 일반적인 feign client 모습과 동일하다.
2.2. 테스트 코드 작성하기
@SpringBootTest(classes = InfraIntegrateTestConfig.class)
@AutoConfigureWireMock(port = 0)
@ActiveProfiles(resolver = InfraIntegrateProfileResolver.class)
@TestPropertySource(
properties = {
"feign.toss.url=http://localhost:${wiremock.server.port}",
// 타임리프때문에 테스트 깨져서 넣음
"spring.thymeleaf.enabled=false"
})
public class TossSettlementClientTest {
설정부분에서 제일 중요한부분은
@AutoConfigureWireMock(port = 0) 로설정해서 randomport 상에서 실행되게끔 하는것이고,
feign 클라이언트가 요청 보낼 주소를 localhost 에 어느 random port 인지는
wiremock.server.port 환경변수로 불러올 수 있다.
이렇게 요청보낼 주소를 정할 수 있다.
@Configuration
@ComponentScan(basePackageClasses = {DuDoongInfraApplication.class, DuDoongCommonApplication.class})
public class InfraIntegrateTestConfig {}
public class InfraIntegrateProfileResolver implements ActiveProfilesResolver {
@Override
public String[] resolve(Class<?> testClass) {
// some code to find out your active profiles
return new String[] {"common", "infrastructure"};
}
}
InfraIntegrateTestConfig , InfraIntegrateProfileResolver는 두둥 프로젝트가 멀티모듈 구조이고
인프라 모듈이 커먼 모듈에 의존성을 가지고 있기 때문에
스프링 부트 테스트를 통한 통합테스트를 진행할 경우 , 스프링 부트 빈 구성을 위해
빈 스캔 범위를 지정하고, 필요한 profile을 편하게 설정하기 위해서 만든 편의 클래스이다.
각 모듈 ( 최종 어플리케이션 모듈이 아닌 의존성이있는 모듈등인 경우 )
application-{모듈이름}.yml 형태로 환경변수들을 세팅해 줄 수 있고,
profile에 지정을 해줘야 해당 모듈의 환경변수들을 불러올 수 있다.
멀티모듈구조가 아니라면 굳이 설정해 줄 필요가 없다.
@Autowired private SettlementClient settlementClient;
@Test
public void 정산요청_올바르게_파싱되어야한다() throws IOException {
// 만들어둔 json 파일을 불러온다.
Path file = ResourceUtils.getFile("classpath:payload/settlement-response.json").toPath();
// import static com.github.tomakehurst.wiremock.client.WireMock.*
stubFor(
// localhost:${wiremock.server.port}/v1/settlements 요청은 willReturn을 한다.
get(urlPathEqualTo("/v1/settlements"))
.willReturn(
aResponse()
.withStatus(HttpStatus.OK.value())
.withHeader(
"Content-Type", MediaType.APPLICATION_JSON_VALUE)
// 바디 지정
.withBody(Files.readAllBytes(file))));
LocalDate now = LocalDate.now();
// 실제 요청이 List<SettlementResponse>에 담겨서온다
List<SettlementResponse> test = settlementClient.execute(now, now, "test", 1, 10);
SettlementResponse settlementResponse = test.get(0);
// 파싱이 제대로 되었는지 확인.. 다 확인은 안하고 디버거로 적당히 했다.!
assertEquals(settlementResponse.getFees().size(), 2);
}
위처럼 코드작성을 하게 되면
실제로 응답이 잘 파싱되는지 확인 할 수 있다.
Enum 값이나 iso 형식으로 넘어오는 날짜 데이타들이 잘 파싱되는것을 볼 수 있다.
이렇게 wiremock 을 활용해서 테스트 코드간에 응답을 미리 지정해서
요청을 보낸뒤에 파싱이 잘되는지 확인해 보았다.
소스 코드는 레포지토리에서 확인 가능하다.
파싱이 잘되는지 볼려면... 매번 요청을 보내고 받았어야 했는데.
이렇게 테스트 작성하는 방법을 파악하고 나니
불편함이 많이 줄었다!
'스프링' 카테고리의 다른 글
[스프링] 멀티모듈 jacoco , sonarqube (cloud) 세팅 (2) | 2023.03.08 |
---|---|
[스프링] spring oauth Open ID Connect with kakao (16) | 2023.03.08 |
[스프링] spring batch 도커로 세팅하기 with jenkins (0) | 2023.03.07 |
[스프링] spring thymeleaf to pdf 이미지,한글 적용하기 (1) | 2023.03.06 |
[스프링] spring rate limit 적용히기 bucket4j (2) | 2023.03.06 |