본문 바로가기
공부 기록

[JAVA x OPENAPI] 공공데이터포털 특일 정보 OpenAPI 사용하기 - 2 (JSON 응답을 JAVA 클래스로 받기 + 테스트)

by 타태 2023. 2. 15.

2023.02.13 - [공부 기록] - [JAVA x OPENAPI] 공공데이터포털 특일 정보 OpenAPI를 활용한 영업일 계산하기- 1 (공공데이터 포털 활용 신청)

 

[JAVA x OPENAPI] 공공데이터포털 특일 정보 OpenAPI를 활용한 영업일 계산하기- 1

2023.01.30 - [공부 기록] - [SPRING RETRY] 바퀴를 다시 발명하지 마라 - @Retrayble을 활용한 재시도 전략 [SPRING RETRY] 바퀴를 다시 발명하지 마라 - @Retrayble을 활용한 재시도 전략 2022.11.12 - [공부 기록] - [SPR

ktae23.tistory.com



이번엔 테스트를 작성하면서 앞서 받아온 json 데이터를 바탕으로 이를 매핑해줄 응답 클래스를 작성해보겠다.

먼저 가볍게 서비스 클래스를 하나 만들고 정말 컴파일만 되고 통과만 될만큼 실제 코드를 작성한다.

HolidayVendorServiceUnitTest

@ExtendWith(MockitoExtension.class)
class HolidayVendorServiceUnitTest {

    @InjectMocks
    private HolidayVendorService holidayVendorService;

    @Test
    @DisplayName("특일 정보를 가져온다.")
    void  get() throws Exception {
        final HolidayResponse holidayResponse = holidayVendorService.get();

        Assertions.assertNotNull(holidayResponse);
    }
}

 

HolidayVendorService

@Service
public class HolidayVendorService {

    public HolidayResponse get() {
        return new HolidayResponse();
    }
}

 

HolidayResponse

public class HolidayResponse {

}

테스트를 수행하면 당연하게도 초록불이 들어 온다.


그럼 이제부터 상세 구현을 시작할 텐데, 먼저 응답 매핑이 잘 되는지 확인이 필요하니 응답 클래스부터 구현하겠다.
이전 시간에 조회해본 응답의 JSON 포맷은 아래와 같다.
이중 내가 필요한 정보는 dateName, locdate 둘 뿐이다.

이전 시간에 선택한 요청은 공휴일만 조회되기 때문에 isHoliday는 항상 "Y"이고 다른 값들은 필요하지 않기 때문이다.
이제 이 응답을 클래스로 작성해보자.

{
  "response": {
    "header": {
      "resultCode": "00",
      "resultMsg": "NORMAL SERVICE."
    },
    "body": {
      "items": {
        "item": [
          {
            "dateKind": "01",
            "dateName": "1월1일",
            "isHoliday": "Y",
            "locdate": 20230101,
            "seq": 1
          },
          {
            "dateKind": "01",
            "dateName": "설날",
            "isHoliday": "Y",
            "locdate": 20230121,
            "seq": 1
          },
          {
            "dateKind": "01",
            "dateName": "설날",
            "isHoliday": "Y",
            "locdate": 20230122,
            "seq": 1
          },
          {
            "dateKind": "01",
            "dateName": "설날",
            "isHoliday": "Y",
            "locdate": 20230123,
            "seq": 1
          },
          {
            "dateKind": "01",
            "dateName": "대체공휴일",
            "isHoliday": "Y",
            "locdate": 20230124,
            "seq": 1
          }
        ]
      },
      "numOfRows": 10,
      "pageNo": 1,
      "totalCount": 5
    }
  }
}


자바 클래스로는 아래 처럼 만들어 주면 된다.
정말 간단하다. json 매핑을 위해 getter만 달아준다.

@Getter
public class HolidayResponse {

    private Response response;

    @Getter
    private static class Response {
        private Body body;
    }

    @Getter
    private static class Body {
        private Items items;
    }

    @Getter
    private static class Items {
        private Item[] item;
    }

    @Getter
    private static class Item {
        private String dateName;
        private String locdate;
    }
}

 



실무에서는 HolidayVendorService를 인터페이스로 두고 환경 별로 다른 HolidayService 구현체를 반환하도록 하고, 응답값도 구분하여 개발환경이나 로컬에서는 외부 요청을 수행하지 않도록 처리하지만 그건 이번 포스팅의 관심사가 아니니 다루지 않겠다.

또한 운영환경에서는 요청하는 년도와 월을 변경해가면서 요청해야 하고 특일정보 API를 개발용으로 인증 받은 경우 API Key가 2년짜리기 때문에 실제로는 YearMonth와 key 값을 인자로 생성되는 HolidayRequest를 만들고 toRequestUrl()란 메서드를 통해 baseURl에 연도와 월 등을 조립하여 URI를 만들도록 구현했다.

예제에서는 테스트가 가능하도록 URL을 바꿔치기 해야하기 때문에 toRequestURl 메서드만을 갖는 깡통 Request를 만들어 사용하겠다.

WebClient 의존을 추가하고 이를 모킹하여 응답이 정상적으로 매핑 되는지를 확인해 보자.
WebClient를 사용한 테스트 방식 중 MockWebServer를 활용해보겠다.

참고 링크 https://www.devkuma.com/docs/mock-web-server/

이제 테스트와 코드는 아래와 같이 바뀐다.

HolidayVendorServiceUnitTest

@ExtendWith(MockitoExtension.class)
class HolidayVendorServiceUnitTest {


    public static MockWebServer mockWebServer;

    private HolidayVendorService holidayVendorService;

    private String baseUrl;

    @BeforeAll
    static void setUp() throws IOException {
        mockWebServer = new MockWebServer();
        mockWebServer.start();
    }

    @BeforeEach
    void initialize() {
        baseUrl = String.format("http://localhost:%s", mockWebServer.getPort());
        holidayVendorService = new HolidayVendorService();
    }

    @AfterAll
    static void tearDown() throws IOException {
        mockWebServer.shutdown();
    }

    @Test
    @DisplayName("특일 정보를 가져온다.")
    void get() {
        String response = "{\n"
            + "  \"response\": {\n"
            + "    \"header\": {\n"
            + "      \"resultCode\": \"00\",\n"
            + "      \"resultMsg\": \"NORMAL SERVICE.\"\n"
            + "    },\n"
            + "    \"body\": {\n"
            + "      \"items\": {\n"
            + "        \"item\": [\n"
            + "          {\n"
            + "            \"dateKind\": \"01\",\n"
            + "            \"dateName\": \"1월1일\",\n"
            + "            \"isHoliday\": \"Y\",\n"
            + "            \"locdate\": 20230101,\n"
            + "            \"seq\": 1\n"
            + "          },\n"
            + "          {\n"
            + "            \"dateKind\": \"01\",\n"
            + "            \"dateName\": \"설날\",\n"
            + "            \"isHoliday\": \"Y\",\n"
            + "            \"locdate\": 20230121,\n"
            + "            \"seq\": 1\n"
            + "          },\n"
            + "          {\n"
            + "            \"dateKind\": \"01\",\n"
            + "            \"dateName\": \"설날\",\n"
            + "            \"isHoliday\": \"Y\",\n"
            + "            \"locdate\": 20230122,\n"
            + "            \"seq\": 1\n"
            + "          },\n"
            + "          {\n"
            + "            \"dateKind\": \"01\",\n"
            + "            \"dateName\": \"설날\",\n"
            + "            \"isHoliday\": \"Y\",\n"
            + "            \"locdate\": 20230123,\n"
            + "            \"seq\": 1\n"
            + "          },\n"
            + "          {\n"
            + "            \"dateKind\": \"01\",\n"
            + "            \"dateName\": \"대체공휴일\",\n"
            + "            \"isHoliday\": \"Y\",\n"
            + "            \"locdate\": 20230124,\n"
            + "            \"seq\": 1\n"
            + "          }\n"
            + "        ]\n"
            + "      },\n"
            + "      \"numOfRows\": 10,\n"
            + "      \"pageNo\": 1,\n"
            + "      \"totalCount\": 5\n"
            + "    }\n"
            + "  }\n"
            + "}";

        mockWebServer.enqueue(new MockResponse()
            .addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
            .setBody(response)
            .setResponseCode(HttpStatus.OK.value())
        );

        final HolidayRequest holidayRequest = mock(HolidayRequest.class);
        when(holidayRequest.toRequestUrl(anyString())).thenReturn(baseUrl);
        final HolidayResponse holidayResponse = holidayVendorService.get(holidayRequest);

        Assertions.assertNotNull(holidayResponse);
    }
}

 

HolidayVendorService

@Service
public class HolidayVendorService {

    private static final String GET_URL = "http://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService/getHoliDeInfo?solYear=2023&solMonth=01&_type=json&numOfRows=30&ServiceKey=[서비스키]";

         public HolidayResponse get(HolidayRequest holidayRequest) {
        return WebClient.builder()
            .baseUrl(holidayRequest.toRequestUrl(GET_URL))
            .defaultHeaders(consumer -> consumer.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)))
            .build()
            .get()
            .exchangeToMono(response ->
                response.bodyToMono(HolidayResponse.class)
                    .onErrorResume(error -> Mono.just(new HolidayResponse()))
            )
            .block();
    }
}

** 추가) 2023년 2월처럼 공휴일이 없는 경우 특일 정보 api 응답이 아래와 같이 온다.

이때 매핑할 locdate와 dateName이 없어서 JSON 예외가 발생하기 때문에 retreive가 아닌  exchangeToMono를 사용하여 예외 발생 시 새로운 객체를 만들어 반환하도록 처리하였다.

{"response":{"header":{"resultCode":"00","resultMsg":"NORMAL SERVICE."},"body":{"items":"","numOfRo ws":30,"pageNo":1,"totalCount":0}}}

items이 없으면 빈 배열이라도 넣어 주지..

 

HolidayResponse

@Getter
public class HolidayResponse {

    private Response response;
    
    @Getter
    static class Response {
        private Body body;
    }

    @Getter
    static class Body {
        private Items items;
    }

    @Getter
    static class Items {
        private Item[] items;
    }

    @Getter
    static class Item {
        private String dateName;
        private String locdate;
    }
}


테스트를 수행하니 매핑이 잘 되어 초록불이 들어 온다.

만일 실패한다면 getter가 잘 선언 되었는지, 또는 Lombok이 정상적으로 getter를 생성해 내는지 확인해본다.
또는 gson과 같은 json 매핑 라이브러리가 없는지 확인해 보도록 하자.

디버깅을 걸어봐도 매핑이 잘 된것을 볼 수 있다.



이제는 해당 정보를 잘 가져와서 데이터베이스에 넣어주기만 하면 그만이다.
그리고 이를 활용해 N 영업일 이전 혹은 이후 계산을 해줄 수 있다.

다음 포스팅에서는 HolidayResponse를 다른 클래스로 다시 한번 컨버팅 하여 꺼내보겠다.
그리고 실제로 특일 정보 API를 잘 받아오는지 컨테이너를 띄워서 테스트 해보고 마치겠다.



2023.02.15 - [공부 기록] - [JAVA x OPENAPI] 공공데이터포털 특일 정보 OpenAPI 사용하기 - 3 (통합 테스트)

 

[JAVA x OPENAPI] 공공데이터포털 특일 정보 OpenAPI 사용하기 - 3 (통합 테스트)

2023.02.15 - [공부 기록] - [JAVA x OPENAPI] 공공데이터포털 특일 정보 OpenAPI 사용하기 - 2 (JSON 응답을 JAVA 클래스로 받기 + 테스트) [JAVA x OPENAPI] 공공데이터포털 특일 정보 OpenAPI 사용하기 - 2 (JSON 응답을

ktae23.tistory.com

 

반응형

댓글