본문 바로가기
TIL : Today I learned (or Week)

TIL 230828 : 테스트코드 3 - Mockito (의존성 있는 Bean객체가 넘나드는 패키지 테스트 실습)

by 우인입니다 2023. 8. 28.

이제 실전이다.

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

이 순간에 필요한 기능, 가짜객체를 편하게 구현해 줄 프레임워크다.

 

세팅하기

  1. @ExtendWith(MockitoExtension.class) 추가
  2. @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/

 

 

https://www.crocus.co.kr/1555