restdocs에서 제약조건(Constraint) 관련 정보가 나오도록 커스텀할 수 있습니다.
import org.springframework.restdocs.constraints.ConstraintDescriptions
public void example() {
ConstraintDescriptions userConstraints = new ConstraintDescriptions(UserInput.class);
List<String> descriptions = userConstraints.descriptionsForProperty("name");
}
static class UserInput {
@NotNull
@Size(min = 1)
String name;
@NotNull
@Size(min = 8)
String password;
}
ConstraintDescriptions 클래스를 이용해 해당 java 클래스의 constraint description 정보를 불러오고, map처럼 property 이름으로 constraint 정보들을 갖고올 수 있습니다.
|===
|Path|Type|Description|Constraints
{{#fields}}
|{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}}
|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}}
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
|{{#tableCellContent}}{{#constraints}}{{.}}{{/constraints}}{{/tableCellContent}}
{{/fields}}
|===
request-fields.snippet 파일은 이런식으로 작성하면 되고, src/test/resources/org/springframework/restdocs/templates/request-fields.snippet 이 경로에 넣어야 합니다.
예제 (번거로운 버전)
package com.vince.interfaces.user
import kotlinx.serialization.Serializable
import javax.validation.constraints.NotEmpty
import javax.validation.constraints.Pattern
import javax.validation.constraints.Size
@Serializable
data class SignupRequest(
@field:NotEmpty
val username: String,
@field:NotEmpty
@field:Size(min = 8)
val password: String,
@field:Pattern(regexp = "010\\d{7,8}")
val phoneNumber: String,
@field:NotEmpty
val name: String,
)
Request dto를 이렇게 만들었다고 했을 때, 이 제약정보를 일일이 description에 넣는건 매우 번거롭고 유지보수가 안돼 restdocs와 실제 constraint 사이 간극이 생기기 쉽습니다
따라서 restdocs에서 제공하는 ConstraintDescriptions 클래스를 사용하면 됩니다
@Test
@DisplayName("회원가입 테스트")
fun signUp() {
val signupRequest = SignupRequest(
"username",
"password",
"01012341234",
"홍길동",
)
val constraints = ConstraintDescriptions(SignupRequest::class.java)
// 회원가입 테스트 코드
// ...
mockMvc.perform(
post("/user/signup")
.contentType(MediaType.APPLICATION_JSON)
.content(Json.encodeToString(signupRequest))
)
.andExpect(status().isOk)
.andDo(
document(
"users/signup",
requestFields(
fieldWithPath("username").description("유저명 (아이디)"),
fieldWithPath("password").description("패스워드")
.attributes(key("constraints").value(
constraints.descriptionsForProperty("password").joinToString("\n")
)),
fieldWithPath("phoneNumber").description("핸드폰 번호")
.attributes(key("constraints").value(
constraints.descriptionsForProperty("phoneNumber").joinToString("\n")
)),
fieldWithPath("name").description("이름"),
),
)
)
}
Path / Type / Description / Constraints
phoneNumber / String / 핸드폰 번호 / Must match the regular expression 010\d{7,8}
대략 코드는 이렇고, 출력은 위처럼 나옵니다
문제는 똑같은 코드가 반복되고 있다는건데 너무 번거롭습니다.
개선된 버전
package com.vince.helper
import org.springframework.restdocs.constraints.ConstraintDescriptions
import org.springframework.restdocs.payload.FieldDescriptor
import org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath
import org.springframework.restdocs.snippet.Attributes.key
class ConstrainedFields(clazz: Class<*>) {
private val constraintDescriptions = ConstraintDescriptions(clazz)
fun withPath(path: String): FieldDescriptor = fieldWithPath(path).attributes(
key("constraints").value(
this.constraintDescriptions.descriptionsForProperty(path)
.joinToString(separator = "\n\n") { "- $it" }
)
)
}
헬퍼 클래스를 테스트 모듈 안에 추가해주시고
@Test
@DisplayName("회원가입 테스트")
fun signUp() {
val signupRequest = SignupRequest(
"username",
"password",
"01012341234",
"홍길동",
)
val fields = ConstrainedFields(SignupRequest::class.java) // (1)
// 회원가입 테스트 코드
// ...
mockMvc.perform(
post("/user/signup")
.contentType(MediaType.APPLICATION_JSON)
.content(Json.encodeToString(signupRequest))
)
.andExpect(status().isOk)
.andDo(
document(
"users/signup",
requestFields(
// (2)
fields.withPath("username").description("유저명 (아이디)"),
fields.withPath("password").description("패스워드"),
fields.withPath("phoneNumber").description("핸드폰 번호"),
fields.withPath("name").description("이름"),
),
)
)
}
이런식으로 쓰면 됩니다. 더이상 constraint attribute를 손수 넣지 않아도 됩니다!
결과물은 이런식으로 나옵니다.
잘 설정하면 locale을 넣을 수도 있을 것 같은데.. 이건 좀 찾아봐야겠네요
참고
- https://docs.spring.io/spring-restdocs/docs/current/reference/html5/#documenting-your-api-constraints
- https://velog.io/@dae-hwa/REST-Docs%EC%97%90-DTO%EC%9D%98-Validation-%EC%A0%95%EB%B3%B4-%EB%8B%B4%EA%B8%B0
- https://github.com/spring-projects/spring-restdocs/blob/main/samples/rest-notes-spring-hateoas/src/test/java/com/example/notes/ApiDocumentation.java
반응형
'프로그래밍 > Kotlin' 카테고리의 다른 글
[Spring Security] HttpSecurity 설정시 kotlin DSL 인식 안될때 (0) | 2023.12.27 |
---|---|
[Kotlin] spring initializr 이용시 'Unresolved reference: snippetsDir' 해결방법 (0) | 2022.07.24 |
[Kotlin] Scope function (범위 함수) (0) | 2022.04.03 |
[Kotlin] flatMap, flatten (0) | 2022.04.02 |
[Kotlin] groupBy (0) | 2022.04.02 |