java.lang.IllegalArgumentException
: 적합하지 않거나(illegal), 적절하지 못한(inappropriate) 인자를 메소드에 넘겨주었을 때 발생

ResponseEntity

: Spring framework에서 제공하는 클래스 중 HttpEntity라는 클래스가 있다. 이것은 HTTP 요청 또는 응답에 해당하는 HttpHeader와 HttpBody를 포함하는 클래스이다.

그리고 HttpEntity 클래스를 상속받아 구현한 클래스가 RequestEntity, ResponseEntity이다.

일반적인 API는 반환하는 리소스에 Value만 갖진 않는다.
(상태코드, 응답 메시지 등이 포함될 수 있다.)

ResponseEntity는 status field를 가져 상태코드를 필수로 반환해주어야 한다.

참고
참고

(Q) Test 작성하다가 생긴 궁금증

🤷🏻‍♀️ repository.save()시 왜 새로운 참조 변수에 담아서 사용해야 해야할까

나랑 똑같은 궁금증을 가진 분이 계셔서 참고해왔다.

save()는 엔티티의 상태에 영향받지 않고 반드시 저장한다.
엔티티를 새로 저장할 수도, 기존 엔티티에 새로운 엔티티를 병합할 수도 있다.
병합시 동일한 id에 연결된 엔티티에 상태를 복사하고 그 엔티티를 반환한다.

즉, 새로 저장하는 경우 build한 엔티티와 save한 엔티티는 동일하지만, 병합하는 경우 다른 엔티티가 된다는 것

그래서 반환된 인스턴스를 사용하지 않는다면 분리된 엔티티 (detached entity)를 수정하게 되어 문제가 발생할 수 있다.

참고

RestTemplate

: Spring 3.0부터 지원하는 템플릿으로 HTTP 통신을 RESTful 형식에 맞게 손쉬운 사용을 제공해주는 템플릿

  • REST API 서비스를 요청 후 응답 받을 수 있도록 설계
  • HTTP 프로토콜의 메소드(GET, POST, PUT, DELETE)들에 적합한 여러 메소드 제공
    - HTTP 요청 후 JSON, XML, String과 같은 응답을 받을 수 있음
  • Header, Content-Type등을 설정해 외부 API 호출
  • Java에서 사용되는 다른 템플릿(Ex. JdbcTemplate)처럼 단순 메소드 호출만으로 복잡한 작업을 쉽게 처리 가능

RestTemplate method

참고

override vs overloading

유효성 검증

Validator
: 스프링에서 도메인 객체를 검증할 수 있도록 제공하는 인터페이스

Controller로 HTTP 요청을 @ModelAttribute 모델에 바인딩 할 때 주로 사용된다.
이 인터페이스는 supports()validate() 메소드로 구성되어 있다.

supports()는 이 검증기가 검증할 수 있는 오브젝트 타입인지 확인해주는 메소드로 이 메소드를 통과한 경우에만 validate()가 호출된다.

유효성 검사 Annotation

  • @Valid: 대상 객체의 확인 조건을 만족하는 경우 통과
    (Spring3 부터 Spring MVC에서는 컨트롤러 메소드의 파라미터를 자동으로 검증하는 이 자동검증 어노테이션을 제공한다.)
  • @Size(min=, max=): 문자열 또는 배열이 지정된 값 사이일 경우 통과
  • @Positive: 양수
  • @UniqueElements: 주석이 달린 Collection의 모든 객체가 고유한지 확인

➡️ 어노테이션 참고, 참고

동작 원리

모든 요청은 디스패처 서블릿을 통해 컨트롤러로 전달된다. 그리고 컨트롤러의 메소드를 호출하는 과정에서는 메소드 값을 처리해주는 ArgumentResolver가 동작한다.
@Valid 또한 ArgumentResolver에 의해 처리된다.

@Valid는 기본적으로 컨트롤러에서만 동작한다.
다른 계층에서 파라미터를 검증하기 위해서는 @Valicated와 결합되어야 한다.

파라미터의 유효성 검증은 컨트롤러에서 처리하고,
서비스나 리포지토리 계층에서는 유효성 검증을 하지 않는 것이 바람직하다.

하지만, 그러지 못할 경우가 발생한다면 아주 경우가 없는 것은 아니다.
Spring은 AOP 기반으로 메소드 요청을 가로채 유효성 검증을 할 수 있도록 @Validated를 제공한다.
(이는 JSR 표준 기술이 아니며 Spring 프레임워크에서 제공하는 어노테이션임)
(컨트롤러, 서비스, 리포지토리 .. 계층에 무관하게 스프링 빈이라면 유효성 검증 가능)

@Valid@Validated에 의한 예외 클래스는 다르다.

  • @Valid: MethodArgumentNotValidException
  • @Validated: ConstraintViolationException

참고
참고

유효성 검사 - BindingResult

: org.springFramework.validation.BindingResult
Errors의 하위 인터페이스로 폼 값을 커맨드 객체에 바인딩한 결과를 저장하고 에러코드로 메시지를 가져옴

Errors 인터페이스의 에러 발생 여부를 확인하기 위한 메소드

  • boolean hasErrors()
    : 에러가 존재할 경우 true 반환
  • int getErrorCount()
    : 에러 개수 반환
  • boolean hasGlobalErrors()
    : reject() 메소드를 이용해 추가된 글로벌 에러가 존재할 경우 true 반환
  • int getGlovalErrorCount()
    : reject() 메소드를 이용해 추가된 에러 개수 반환
  • boolean hasFieldErrors()
    : rejectValue() 메소드를 이용해 추가된 에러 발생할 경우 true 반환
  • int getFieldErrorCount()
    : rejectValue() 메소드를 이용해 추가 에러 개수 반환
  • boolean hasFieldErrors(String field)
    : rejectValue() 메소드 이용해 추가한 특정 필드의 에러가 존재할 경우 true 반환
  • int getFieldErrorCount(String filed)
    : rejectValue() 메소드를 이용해 추가한 특정 필드의 에러 개수 반환

참고 (→ 반환타입 void도 있음)
참고

Validation 모범사례

  • getFieldErrors
    : <FieldError> getFieldErrors() 나열
    필드와 관련된 모든 오류를 가져옴
  • getFieldError
    : 필드와 관련된 오류가 있는 경우 첫번째 오류를 가져옴

(Q) BindingResult 테스트 시 문제점

Controller를 테스트 하려는데 나의 Controller에서 메소드는 BindingResult를 파라미터로 받고있다.
그래서 테스트시 어떻게 파라미터를 넣어주어야 할지 잘 모르겠다.

구글링으로 찾아본 글에 의하면

@Valid 어노테이션을 사용할 때 BindingResult를 Mock 객체로 만들어야 할 때가 있다.

이게 내가 궁금해하던 질문인 것 같다. 어떻게 이 객체를 만들어야 하는지?
그래서 해답은 Mock 객체를 사용할 수 있도록 setup 코드를 삽입하면 해당 BindingResult를 무시하고 테스트를 작성할 수 있다.

의문에 대한 답이 될만한 글

(프로젝트에서 테스트 메소드 시작 전 Mock 객체를 미리 받아놓고 구현하면 됨)

gson

: Java에서 Json을 파싱하고 생성하기 위해 사용되는 구글 오픈 소스
Java Object를 Json 문자열로 변환할 수 있고, Json 문자열을 Java Object로 변환할 수 있음

build.gradle에 의존성 추가

	implementation 'com.google.code.gson:gson:2.8.7'

Spring MVC Exception

참고

상태코드, 응답 메시지 클래스로 빼서 사용하기

참고

Spring Controller에서 사용자 지정 상태코드 반환하기

참고

@RequestBody VS @RequestParam

  • @RequestBody
    : 요청한 값을 String으로 전달 받을 수 있다.
    Ex. 'name=yoon&age=100'
  • @RequestParam
    : 데이터를 저장하는 이름으로 메소드의 변수명을 설정해주면 받고싶은 데이터를 그 변수에 할당받을 수 있다.
    Ex. (name을 설정해주었을 경우) yoon만 값으로 전달받을 수 있음

🔗이 글을 보면 잘 이해할 수 있을 것이다.

ExceptionHandler

  • @ControllerAdvice
    : 컨트롤러를 보조하는 클래스
    @Controller 어노테이션이 있는 모든 곳에서 예외를 잡을 수 있게 해준다.
    @ControllerAdvice 안에 있는 @ExceptionHandler는 모든 컨트롤러에서 발생하는 예외 상황을 잡을 수 있다.

    즉, @Controller 어노테이션을 갖거나, xml 설정 파일에서 컨트롤러로 명시된 클래스에서 Exception이 발생하면 감지한다.
    (@RestControllerAdvice 어노테이션과 유사)
    Controller와 RestController만 ExceptionHandler의 감시 대상이 된다. (Service만 감시 대상으로 등록할 수는 없음)
    하지만 Controller와 Service를 호출한 경우, Service에서 Exception이 발생해도 결국 Controller로부터 문제가 발생했음을 감지하게 때문에 Handler가 작동한다.
    (그리고 이 어노테이션으로 특정한 클래스만 명시할 수도 있다.)

  • @ResponseBody
    : @RestController = @Controller + @ResponseBody
    ExceptionHandler는 controller 혹은 restController가 아니다.
    응답 값은 컨트롤러처럼 String 또는 ModelAndView만 가능하다.
    따라서 응답 객체를 반환하고자 한다면 @ResponseBody 어노테이션을 메소드 위 명시해주어야 한다.
  • @ExceptionHandler
    : @Controller, @RestController가 적용된 Bean에서 발생하는 예외를 잡아 하나의 메소드에서 처리해주는 기능

    @ExceptionHandler에 설정한 예외가 발생하면 handler가 실행된다.
    @Service, @Repository가 적용된 Bean에서는 사용할 수 없다.

    @ControllerAdvice 가 명시된 클래스 내부 메소드에 사용한다.
    Attribute로 Exception 클래스를 받는다.

    즉, RuntimeException.class 또는 그보다 더 상위 클래스인 Exception.class 등을 넘기면 된다.

    @ExceptionHandler(XXException.class)라고 작성한 경우, @ControllerAdvice에서 명시한 클래스에서 throw new XXException(...)이 발생하면 핸들러는 이를 감지하고 해당 메소드를 수행한다.

    메소드는 여러개 작성할 수 있으며 이에 따라 @ExceptionHandler에 다른 Attribute 값을 넘김으로써 각 예외를 다르게 처리할 수 있다.

    value를 통해 어떤 예외를 잡을지 지정할 수 있다. 하지만 지정하지 않으면 모든 예외를 잡아버린다.
    물론 여러 예외를 한꺼번에 정할 수도 있다. (Ex. @ExceptionHandler({Exception.class, ...}) ➡️ List 형태로)

참고

default 접근 제어자

: 접근 제어자를 별도로 설정하지 않는다면 접근 제어자가 없는 변수, 메소드는 default 접근 제어자가 되어 해당 패키지 내에서만 접근 가능

@Mock

: 가짜 객체
실제 객체를 만들어 사용하기에는 시간, 비용이 많이 들거나 객체간 의존성이 강해 구현하기 힘들 경우 가짜 객체를 만들어 사용하는 방법이다.

Mock 객체가 필요한 경우

  • 테스트 작성을 위한 환경 구축이 어려운 경우
  • 테스트가 특정 경우나 순간에 의존적인 경우
  • 테스트 시간이 오래 걸리는 경우 (또한, 성능 문제로 인한 시간을 단축시키기 위해서도 사용)

기본적인 분류 개념, 테스트 더블

1. 테스트 더블 (Test Couble)
: 테스트계의 스턴트맨 = 즉, 대역

  • 테스트를 진행하기 어려울 경우 이를 대신해 테스트를 진행할 수 있도록 만들어주는 객체
  • Mock 객체와 유사한 의미를 가지며 테스트 더블이 조금 더 상위 의미로 사용됨

2. 더미객체 (Dummy Object)

  • 단순히 인스턴스화 될 수 있는 수준으로만 객체를 구현
  • 인스턴스화 된 객체가 필요할 뿐 해당 객체의 기능까지는 필요하지 않은 경우에 사용

3. 테스트 스텁 (Test Stub)

  • 더미 객체보다 조금 더 구현된 객체로 더미 객체가 실제로 동작하는 것처럼 보이도록 만들어 놓은 객체
  • 객체의 특정 상태를 가정해 만들어 특정 값을 리턴해 주거나 특정 메시지를 출력해 주는 작업을 수행
  • 특정 상태를 가정해 하드코딩 된 형태로 로직에 따른 값의 변경은 테스트할 수 없음
    즉, 어떤 행위가 호출되었을 때 특정 값으로 반환해주는 형태가 Stub

4. 페이크 객체 (Fake Object)

  • 여러 상태를 대표할 수 있도록 구현된 객체로 실제 로직이 구현된 것처럼 보이게 함
  • 실제 DB에 접속해서 비교할 때와 동일한 모양이 보일 수 있도록 객체 내부에 구현할 수 있음
    - 테스트 케이스 작성을 위해 다른 객체들과의 의존성을 제거하기 위해 사용
    - 페이크 객체를 만들 때 복잡도로 인해 노력이 많이 들어갈 경우 적절한 수준에서 구현하거나 Mock 프레임워크를 사용
    - 페이크 객체를 생성하기 위한 노력이 많이 필요한 경우 실제 객체를 가져와 테스트

5. 테스트 스파이 (Test Spy)
: @Spy로 만든 mock 객체는 진짜 객체이며 메소드 실행시 스터빙하지 않으면 기존 객체의 로직을 실행한 값을, 스터빙한 경우에는 스터빙 값을 반환함

  • 테스트에 사용되는 객체, 메소드의 사용 여부 및 정상 호출 여부를 기록하고 요청시 알려줌
  • 테스트 더블로 구현된 객체에 자기 자신이 호출되었을 때 확인이 필요한 부분을 기록하도록 구현
  • 특정 테스트 메소드가 몇 번 호출되었는지 필요한 경우 에는 전역 변수로 카운트를 설정하고 특정 메소드에 카운트를 올리는 부분을 추가한 후 이 카운트를 가져오는 메소드를 추가
  • 특정 메소드가 호출되었을 때 또 다른 메소드가 실행이 되어야 한다와 같은 행위 기반 테스트가 필요한 경우 사용

6. Mock 객체 (Mock Object)
: 샘플 휴대폰 = 즉, 가짜

  • 행위를 검증하기 위해 사용되는 객체를 지칭하며 수동으로 만들 수도 있고 프레임워크를 통해 만들 수도 있음
  • 행위 기반 테스트는 복잡도나 정확성 등 작성하기 어려운 부분이 많아 상태 기반 테스트가 가능하다면 만들지 않는 것이 좋음
  • Mock 객체는 테스트 더블 하위 객체로써의 좁은 의미와 테스트 더블을 포함한 넓은 의미 이렇게 두가지로 사용될 수 있음

Mock Object는 행위 검증(behavior verification)에 사용하고 Stub은 상태 검증(state verification)에 사용

@Mock으로 만든 mock 객체는 가짜 객체이며 그 안에 메소드를 호출해 사용하려면 반드시 stubing을 해야한다.
스터빙하지 않고 그냥 호출한다면 primitive type은 0, 참조형은 Null을 반환한다.

참고
참고

Stubbing

: Mock 객체의 행동을 조작하는 것

stub

  • 호출되면 미리 준비된 답변으로 응답
  • 테스트시 프로그램 된 것 이외에는 응답 X
  • 협력객체의 특정 부분이 테스트하기 힘들 경우 stub을 사용하면 수월하게 테스트 가능

mock

  • 다른 테스트 더블과는 다르게 행위 검증 사용을 추구
  • 행위를 기록하는 식의 로직이 포함됨
profile
개발 바보 이사 중

0개의 댓글