|
2 | 2 | 
|
3 | 3 |
|
4 | 4 | ## Spring Rest Docs가 뭐야
|
| 5 | +`Spring Rest Docs`는 `Swagger`와 마찬가지로 `Open API`를 생성할 수 있는 라이브러리입니다. |
| 6 | + |
| 7 | +`Swagger`는 `Controller`단에 어노테이션을 붙여 `API`에 대한 설명을 덧붙일 수 있지만 |
| 8 | +`Spring Rest Docs`는 테스트 코드를 작성함으로써 `Open API`를 구체화할 수 있습니다. |
| 9 | + |
| 10 | +또한 `Swagger`는 어노테이션 기반이기 때문에 구체화한 정보에 대한 검증이 이루어지지 않고 |
| 11 | +그에 따라 `API`가 변하면서 바뀌어야하는 조건에 대한 정확성과 무결성이 깨지게 됩니다. |
| 12 | +하지만 `Spring Rest Docs`는 테스트 코드에서 실제 `Request`가 오지 않았거나, `Response`에 대한 결과가 맞지 않거나, |
| 13 | +타입이 맞지 않는 등의 `실제와 다른 정보`를 검증이 가능합니다. |
| 14 | +따라서 `Open API`와 실제 `API`의 동작 방식간의 일관성을 유지할 수 있습니다. |
| 15 | + |
| 16 | +## Spring Rest Docs 사용해보기 |
| 17 | +```kotlin |
| 18 | + |
| 19 | +@Test |
| 20 | +fun `모든 학생 조회하기 - OK`() { |
| 21 | + val returnValue = listOf( |
| 22 | + StudentSearchDto( |
| 23 | + studentId = 1, |
| 24 | + studentName = "jinhyeok lee", |
| 25 | + studentGradeClassNumber = "3417", |
| 26 | + ), |
| 27 | + StudentSearchDto( |
| 28 | + studentId = 2, |
| 29 | + studentName = "jinhyuk lee", |
| 30 | + studentGradeClassNumber = "3517", |
| 31 | + ), |
| 32 | + ) |
| 33 | + |
| 34 | + given(studentSearchService.searchAll()) |
| 35 | + .willReturn(returnValue) |
| 36 | + |
| 37 | + val result = mockMvc.perform( |
| 38 | + createRequest<Nothing>( |
| 39 | + httpMethod = HttpMethod.GET, |
| 40 | + url = "/students", |
| 41 | + )) |
| 42 | + .andExpect(status().isOk) |
| 43 | + .andDo( |
| 44 | + document( |
| 45 | + "모든 학생 조회하기 - OK", |
| 46 | + getDocumentRequest(), |
| 47 | + getDocumentResponse(), |
| 48 | + responseFields( |
| 49 | + fieldWithPath("response.students") |
| 50 | + .type(JsonFieldType.ARRAY) |
| 51 | + .description("조회한 학생들"), |
| 52 | + fieldWithPath("response.students[].id") |
| 53 | + .type(JsonFieldType.NUMBER) |
| 54 | + .description("학생 아이디"), |
| 55 | + fieldWithPath("response.students[].name") |
| 56 | + .type(JsonFieldType.STRING) |
| 57 | + .description("학생 이름"), |
| 58 | + fieldWithPath("response.students[].gradeClassNumber") |
| 59 | + .type(JsonFieldType.STRING) |
| 60 | + .description("학생 학년-반-번호"), |
| 61 | + fieldWithPath("errorCode") |
| 62 | + .type(JsonFieldType.NULL) |
| 63 | + .description("에러 코드"), |
| 64 | + fieldWithPath("errorMessage") |
| 65 | + .type(JsonFieldType.NULL) |
| 66 | + .description("에러 메시지"), |
| 67 | + ), |
| 68 | + ), |
| 69 | + ) |
| 70 | + .andReturn() |
| 71 | + .response |
| 72 | + .contentAsString |
| 73 | + .toObject<CommonResponse<StudentAllSearchResponse>>() |
| 74 | + |
| 75 | + verify(studentSearchService).searchAll() |
| 76 | + |
| 77 | + assertThat(result.errorCode).isNull() |
| 78 | + assertThat(result.errorMessage).isNull() |
| 79 | + assertThat(result.response).isNotNull |
| 80 | + assertThat(result.response!!.students) |
| 81 | + .map<Long> { it.id } |
| 82 | + .contains(1, 2) |
| 83 | + assertThat(result.response!!.students) |
| 84 | + .map<String> { it.name } |
| 85 | + .contains("jinhyeok lee", "jinhyuk lee") |
| 86 | + assertThat(result.response!!.students) |
| 87 | + .map<String> { it.gradeClassNumber } |
| 88 | + .contains("3417", "3517") |
| 89 | +} |
| 90 | +``` |
| 91 | +테스트 프레임워크는 `BDD-Mockito`를 이용하였습니다. |
| 92 | +겉보기에는 컨트롤러를 테스트하는 코드로 보이지만 눈에 띄는 코드가 있습니다. |
| 93 | +`andDo` 메소드를 통해 `document`를 생성하는 코드입니다. |
| 94 | +여기서는 위에서 말했던 것처럼 `Request`와 `Response`를 검증하는 역할을 합니다. |
| 95 | + |
| 96 | +첫 번째 인자로는 `API`의 이름을 등록하고, |
| 97 | +두, 세 번째 인자로는 `Request`와 `Response`에 대한 설정을 등록하는데 |
| 98 | +여기서는 나중에 테스트 코드를 빌드하여 생성되는 `asciidoc`에서 |
| 99 | +`Request`와 `Response`를 예쁘게 찍기 위한 설정을 해두었습니다. |
| 100 | + |
| 101 | +제일 중요한 건 마지막 인자입니다. |
| 102 | +여기서 `Response`를 검증하기 위해서는 `responseFields()` 메소드를, |
| 103 | +`Request`를 검증하기 위해서는 `requestFields()` 메소드를 사용합니다. |
| 104 | +다른 내용은 한 눈에 봐도 무슨 뜻인지 유추하기 쉽습니다. |
| 105 | + |
| 106 | +이렇게 도큐먼트를 생성한 뒤에는 원래 테스트 하던 것처럼 |
| 107 | +`verify` 해주고, 응답 `assertion` 하시면 됩니다. |
0 commit comments