이제 실전이다.
Bean객체가 여기저기 넘나들며 주입된 스프링 프로젝트를 실습해보려 한다.
+물론 스프링의 동작과정을 뜯어보기 위한 공부도 있지만, 계속 미뤄두고 있고 언젠간.. 돌파해본다.
우선 UserService 적용
Controller 테스트는 뭔가 머리아프다. 일단 Service로 간다. 그 중에서 User회원가입을 테스트 해본다.
문제 : Given - When - Then 작성하다보니..
생각해보니 signup 메소드는 void 리턴하는 메소드이다.
- 무슨 값으로 검증을 하지?
- 회원이 가입되었는지 조회를 해야하나?
- 그건 Repository 를 조회해야하는 건데 Service단위만 테스트 하고 싶은데.
가짜 객체의 필요성
UserService를 생성하려고 하니, 기존 클래스에서는 주입받아왔던 것들을 당연하게도 여기서도 주입해줘야 한다.
지금과 같은 상황처럼 Service클래스와 연관된 다른 객체들을 어떻게 처리해줘야할 지 곤란하다.
여기서는 UserRepository에 유저가 잘 저장되는지 확인해야하는데 Repository 객체를 직접 가져오게 되면 단위테스트의 의미가 무색해진다.
이 때 가짜 객체, 즉 Mock객체가 필요하다.
+테스트더블
테스트 더블이라는 개념이 있는데, 단순한 객체를 이용해 테스트를 하게되고, 이때 그 방법중 하나로 Mock이 있다.
테스트 더블 - Dummy, Fakes, Mocks, Spy and Stubs (https://codinghack.tistory.com/92)
위 표를 보면 알 수 있듯 Dummy -> Stub -> Spy -> Mock 의 방향으로 기능이 추가된다.
- Dummy : 존재만 하고 기능도 반환값도 없는 껍데기.
- Stub : 상태를 검증 가능하다.
Dummy데이터가 반환값이 없었지만 Stub에는 특정 메서드 호출에 응답값을 지정해둘 수 있다. - Fake : 동작만 구현되어있지만 사용할 수는 없다. 테스트용 객체.
- Spy : Stub의 역할에다가 추가로 약간의 정보가 더 있다. ex. 실행 카운트 메소드를 가지고 있다거나.
- Mock : Mock은 행위를 검증 가능하다. 이는 복잡도나 정확성 등이 어려운 부분이 많다.
Mockito
이 순간에 필요한 기능, 가짜객체를 편하게 구현해 줄 프레임워크다.
세팅하기
- @ExtendWith(MockitoExtension.class) 추가
- @Mock 추가로 목객체 선언.
Mock객체 주입해주기
이렇게 구색만 갖춰주면 userService 객체를 쓸 수는 있다.
여기서 확인하고자 하는 메소드는 userService.signUp(requestDto, null); 이기 때문에 기존에 주입받았던 것을 Mock객체로 대체하고 테스트만 진행하는 개념이다.
중복된 유저로 인한 가입 실패 케이스
given
////Given
//이미 가입된 유저 객체 생성
User user = new User(requestDto);
log.info(requestDto.getUsername());
//회원가입 정보로 만든 동일한 User객체를 반환해줘, 기존 유저랑 중복되는 상황 구현.
given(userRepository.findByUsername(requestDto.getUsername())).willReturn(Optional.of(user));
여기서 given( ).willReturn( ); 문법은 BDDMockito의 메소드이다.
userservice.signUp( ); 메소드 안에서 userRepository.findByUsername( );이 제대로 작동할 리가 없다.
Mock객체이기 때문이다.
그래서 미리 정해둔 리턴값을 명시적으로 넣어줌으로써 확인할 수 있는 것이다.
애당초 확인하고자 하는 과정은 저렇게 찾았을 경우에 중복을 발견하고 예외를 던지냐는 것이기 때문이다.
When-Then
////WHEN - THEN
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> userService.signup(requestDto, null));
assertEquals("중복된 Username이 존재합니다.", exception.getMessage());
이렇게 예외가 발생하게끔 signup()을 실행해주고, 예상되는 예외 메시지까지 Assertions 해줬다.
테스트 결과
더 공부할 것
어떤 경우에 @Autowired vs @Mock 해야하는 지 가늠이 안된다.
ex. PasswordEncoder : 굳이 Mock으로 해줘야하는지. 굳이 주입받아와야할 지?)
테스트코드를 잘 짠다라는 것은 뭘까?
생각보다 하나하나 다하면 엄청많고 당연히 구현되어야 할 것들도 테스트케이스로 해주는 느낌도 있다.
물론 만에 하나를 위한 반드시 해야할 비상장치같은 느낌도 있다.
Mockito vs BDDMockito 차이
https://tecoble.techcourse.co.kr/post/2020-09-29-compare-mockito-bddmockito/
'TIL : Today I learned (or Week)' 카테고리의 다른 글
WIL 230903 : 처음엔 낯설고 낯선게 어렵다. (+Redis도) (0) | 2023.09.04 |
---|---|
TIL 230831 : 코딩테스트하며 쓰인 Map, Set 문법들 (0) | 2023.09.01 |
WIL 230827 : 테스트 코드 정면돌파 (0) | 2023.08.28 |
TIL 230825 : 테스트 코드 2 - 의존성 없는 패키지 테스트 실습 (0) | 2023.08.28 |
TIL 230824 : 테스트 코드 1 - JUnit이란? 단위테스트 (0) | 2023.08.27 |