본문 바로가기
공부/프로그래밍

[spring] 객체 내 객체에 @Valid 점검하기

by demonic_ 2021. 1. 25.
반응형

예를들어 다음과 같은 데이터 구성이 있다.

유저정보 내 연결된 SNS 계정을 등록해야 한다.

그런데 SNS계정은 1개 이상 필수이며, SNS 유형과 ID가 필수여야한다.

 

클래스로 표현하면 다음과 같다.

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class RequestUser {
    @NotNull(message = "유저이름은 필수입니다")
    private String userName;

    @NotNull(message = "SNS ID는 1개 이상 등록되어야 합니다(1)")
    @Size(min = 1, message = "SNS ID는 1개 이상 등록되어야 합니다(2)")
    private List<SnsAccount> snsAccounts;
}

@Getter
@NoArgsConstructor
@AllArgsConstructor
class SnsAccount {
    @NotNull(message = "SNS 유형은 필수입니다")
    private String snsType;

    @NotNull(message = "SNS id는 필수입니다")
    private String snsId;
}

 

SnsAccount를 @RequestBody로 받아오려 하는데, 해당 클래스의 null, 크기, 그리고 그 안의 객체의 각 값들이 제대로 들어있는지를 점검하는 것이다.

위 상태에서 valid를 실행하면 테스트가 모두 통과된다. 즉 SnsAccount 내 값이 null이라 하더라도 넘어간다는 뜻이다.

 

우선 점검하고 싶은것 위주로 하나씩 테스트 작성해보자.

 

우선은 null을 체크하는 것부터 만들었다.

import org.hibernate.validator.HibernateValidator;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

import javax.validation.ConstraintViolation;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

class SnsAccountTest {
    private LocalValidatorFactoryBean validator;

    @BeforeEach
    void init() {
        validator = new LocalValidatorFactoryBean();
        validator.setProviderClass(HibernateValidator.class);
        validator.afterPropertiesSet();
    }

    @Test
    @DisplayName("notNull 체크")
    void checkNotNullTest() {
        // given
        RequestUser request = new RequestUser("아이유", null);

        // when
        Set<ConstraintViolation<RequestUser>> validate = validator.validate(request);
        Iterator<ConstraintViolation<RequestUser>> iterator = validate.iterator();

        assertThat(true).isEqualTo(iterator.hasNext());

        // then
        while (iterator.hasNext()) {
            ConstraintViolation<RequestUser> next = iterator.next();
            String message = next.getMessage();
            System.out.println("message = " + message);
            assertThat("SNS ID는 1개 이상 등록되어야 합니다(1)").isEqualTo(message);
        }
    }
}

 

 

SnsAccouts 에 값이 null 이므로 관련 체크가 되었고, 메세지를 리턴했다.(테스트 정상)

 

그럼 이번에는 size가 0 이상인 것을 체크해보도록 하자

...
    @Test
    @DisplayName("list 에서 1개이상 등록")
    void checkListMore1Test() {
        // given
        List<SnsAccount> snsAccounts = new ArrayList<>();
        SnsAccount snsAccount1 = new SnsAccount();
        snsAccounts.add(snsAccount1);
        RequestUser request = new RequestUser("아이유", snsAccounts);

        // when
        Set<ConstraintViolation<RequestUser>> validate = validator.validate(request);
        Iterator<ConstraintViolation<RequestUser>> iterator = validate.iterator();

        // then
        assertThat(false).isEqualTo(iterator.hasNext());
    }
...

 

마찬가지로 성공했다.

사실 이 테스트는 실패했어야 한다. 왜냐하면 SnsAccount 내에 @NotNull을 설정해두었기 때문이다. 코드를 보면 snsAccount1을 생성할때 값이 아무것도 없는 기본생성자로 생성했기에 안내문구가 나와야 하는데 그러지 않았다.

 

List에 포함되는 클래스도 Valid 체크를 하려면 List 위에 @Valid 어노테이션을 추가해야한다. 다음과 같다.

...
    @Valid
    @NotNull(message = "SNS ID는 1개 이상 등록되어야 합니다(1)")
    @Size(min = 1, message = "SNS ID는 1개 이상 등록되어야 합니다(2)")
    private List<SnsAccount> snsAccounts;
...

@Getter
@NoArgsConstructor
@AllArgsConstructor
class SnsAccount {
    @NotNull(message = "SNS 유형은 필수입니다")
    private String snsType;

    @NotNull(message = "SNS id는 필수입니다")
    private String snsId;
}

 

똑같은 테스트를 다시 실행해보면 이번엔 실패한다.

 

에러가 발생했기 때문에 iterator.hasNext() 가 true인 것이다.

테스트코드를 조금 수정해서 메세지를 살펴보자.

...
    @Test
    @DisplayName("list 에서 1개이상 등록")
    void checkListMore1Test() {
        // given
        List<SnsAccount> snsAccounts = new ArrayList<>();
        SnsAccount snsAccount1 = new SnsAccount();
        snsAccounts.add(snsAccount1);
        RequestUser request = new RequestUser("아이유", snsAccounts);

        // when
        Set<ConstraintViolation<RequestUser>> validate = validator.validate(request);
        Iterator<ConstraintViolation<RequestUser>> iterator = validate.iterator();

        assertThat(true).isEqualTo(iterator.hasNext());

        // then
        while (iterator.hasNext()) {
            ConstraintViolation<RequestUser> next = iterator.next();
            String message = next.getMessage();
            System.out.println("message = " + message);
        }
    }
...

 

이제 null에 대한 메세지처리가 제대로 나온다.

 

그럼 이제 통과할 테스트를 작성해보자.

...
    @Test
    @DisplayName("sns not null 체크")
    void checkListNotNullTest() {
        // given
        List<SnsAccount> snsAccounts = new ArrayList<>();
        SnsAccount snsAccount1 = new SnsAccount("KAKAO", null);
        snsAccounts.add(snsAccount1);
        RequestUser request = new RequestUser("아이유", snsAccounts);

        // when
        Set<ConstraintViolation<RequestUser>> validate = validator.validate(request);
        Iterator<ConstraintViolation<RequestUser>> iterator = validate.iterator();

        assertThat(true).isEqualTo(iterator.hasNext());

        // then
        while (iterator.hasNext()) {
            ConstraintViolation<RequestUser> next = iterator.next();
            String message = next.getMessage();
            System.out.println("message = " + message);
            assertThat("SNS id는 필수입니다").isEqualTo(message);
        }
    }
...

 

SNS ID는 일부로 Null을 넣어 비교했고 테스트 성공이 되었다.

 

 

결론

서브클래스에 유효성을 체크하고 싶다면 해당 클래스를 변수에 담는 곳에 @Valid를 추가한다.

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class RequestUser {
    @NotNull(message = "유저이름은 필수입니다")
    private String userName;

    @Valid      // <<<<<<<<<< 이부분
    @NotNull(message = "SNS ID는 1개 이상 등록되어야 합니다(1)")
    @Size(min = 1, message = "SNS ID는 1개 이상 등록되어야 합니다(2)")
    private List<SnsAccount> snsAccounts;
}

 

 

끝.

반응형

댓글