본문 바로가기
💊 Java & Kotlin & Spring/- Spring

[JUnit 5] @ParameterizeTest 로 하나의 테스트 코드에서 여러 개의 파라미터 테스트하기 (Springboot + ControllerAdvice, 비밀번호 검증)

by Wonit 2021. 4. 19.

오늘은 JUnit5 의 @ParameterizedTest 에 대해서 알아볼 것이다.
이를 위해 사용되는 필요 기술은 다음과 같다.

  • SpringBoot 2.
  • JUnit 5
  • Validation
  • ControllerAdvice

이와 같은 DTO 객체가 존재한다고 가정해보자.

@NotBlank@Size@Email 은 모두 validation 에서 사용하는 것인데, 만약 이 입력 값들 중에서 해당 constraint를 만족시키지 못한다면 MethodArgumentNotValidException 을 발생시킨다.

 

 

그럼 ControllerAdvice를 이용해서 해당 예외가 발생하였을 때 ErrorResponse를 응답하는 구조로 만들어보자.

 

ControllerErrorAdvice.class

@ResponseBody
@ControllerAdvice
public class ControllerErrorAdvice {
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ErrorResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException exception) {
        return new ErrorResponse(exception.getBindingResult().getAllErrors().get(0).getDefaultMessage());
    }
}

 

해당 ControllerAdvice 에서 @ExceptionHandler 로 어떤 예외가 발생하였는지를 잡아서 @ResponseStatus에 우리가 내려줄 상태 코드를 명시해준다.

 

DTO

public class ErrorResponse {
    private String message;

    public ErrorResponse(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

 

해당 예외에 지정될 message 필드를 받아 클라이언트에게 전달해줄 DTO 객체이다.

 

ControllerTest

@SpringBootTest
class ControllerTest {

    @Autowired
    private MemberService memberService;

    @ParameterizedTest // 1
    @DisplayName("회원 가입 - 비정상적 입력")
    @MethodSource("paramsForSignUpInvalidInputs") // 2
    void signUp_with_invalid_inputs(String email, String password, String nickname) throws Exception{
        // given
        Member member = Member.builder()
                .email(email)
                .password(password)
                .nickname(nickname)
                .build();
        // when & then
        MemberRequestSignUpData memberRequestData = modelMapper.map(member, MemberRequestSignUpData.class);
        mockMvc.perform(post("/api/members")
                    .contentType(MediaType.APPLICATION_JSON)
                    .accept(MediaType.APPLICATION_JSON)
                    .content(objectMapper.writeValueAsString(memberRequestData)))
                .andDo(print())
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("message").exists());
    }

    private static Stream<Arguments> paramsForSignUpInvalidInputs() { // 3
        return Stream.of(
                Arguments.of("noEmail", "test1234", "James"), // 이메일 형식이 아님
                Arguments.of("dhslrl321@gmail.com", "noPw", "James"), // 비밀번호가 8글자 미만
                Arguments.of("dhslrl321@gmail.com", "test1234", "1"), // 닉네임이 2글자 미만
                Arguments.of("", "", "") // 값이 비어있는 경우
        );
    }
}

 

이와 같은 형태로 사용된다.


아직 무슨 내용인지 몰라도 괜찮다.

 

하나 하나 알아보자.

 

주석 1. @ParameterizedTest

 

ParameterziedTest 는 JUnit 4에서 사용되었던 @RunWith(Parameterized.class) 와 동일한 역할을 수행한다.

 

테스트 메서드 하나를 이용해서 다양한 매개변수를 이용한 테스트 결과를 제공하는데,

 

 

JUnit 공식 문서에서 @ParameterizedTest는 다음과 같이 표현된다.

 

@ArgumentsSource 나 해당 주석에 대응하는 필드를 이용해서 전달할 매개 변수를 지정해야 하는데, 해당 어노테이션을 사용하는 테스트 메서드는 private 이나 static 이어서는 안된다.

 

다양한 Source를 이용해서 파라미터를 넘길 수 있다.

 

다양한 Source

  • @ValueSource : string, int 형식의 primtive 타입을 전달할 수 있다.
    • @ValueSource(strings = {"a", "b", "c"})
  • @NullSource : Null 값을 파라미터로 넣어준다.
  • @EmptySource :
  • @EnumSource : Enum 타입의 value를 파라미터로 넣어준다.
    • @EnumSource(value = UserRole.class, name = {"ADMIN", "USER"}
  • @MethodSource
    • 가장 중요한 Source로 더 자세히 알아보자.

주석 2. @MethodSource

 

해당 어노테이션이 가장 중요하다는 데에는 이유가 있다.

 

위에서 보았던 방식의 어노테이션은 하나의 타입만을 파라미터로 넘길 수 있는데, @MethodSource를 사용하면 여러 타입을 넘기는 것이 가능해진다.

 

이게 왜 중요한걸까?

우리가 테스트할 대상은 하나의 타입만으로 이루어져있지 않고 복잡한 타입으로 구성되어있을 가능성이 매우 크기 때문이다.

 

앞서 보았던 테스트 메서드에 조금 변형을 시켜보자.

  • email: String
  • age: int
  • UserType: UserType.Enum
  • nickname: String

이라고 한다면 벌써 3개의 타입이 존재한다.

 

이런 경우 @MethodSource를 사용할 수 있는 것이다.

 

@MethodSource는 메서드에서 반환되는 스트림을 이용해서 테스트의 파라미터로 넘기는데, 파라미터로 전달될 스트림을 반환하는 메서드를 작성해주면 된다.

 

아래서 더 자세히 알아보자.

 

주석 3. paramsForSignUpInvalidInputs()

private static Stream<Arguments> paramsForSignUpInvalidInputs() 의 형태를 보면 알겠지만 해당 메서드에서 주목해야할 점이 2개가 존재한다.

 

  • static
  • Stream

앞서 말 했듯 MethodSource는 스트림을 반환해야 한다고 했다.

 

우리는 arguments 제너릭을 이용한 스트림을 반환할 것이고 해당 어노테이션은 꼭 static 메서드여야 한다.

 

 

테스트 코드 돌려보기

 

위와 같은 메서드가 넘기는 Stream으로 테스트 코드를 돌려보자.

 

 

 

그럼 정상적으로 우리의 기능이 잘 동작하는 것을 볼 수 있다.

 

댓글0