Spring Boot/스프링 부트 핵심 가이드

[SpringBoot] 7.4.7 서비스 테스트

nayonsoso 2023. 9. 29. 19:25
Summary :
서비스 테스트는
given - 레포지토리의 메소드가 특정 값을 리턴한다고 가정 (given / willReturn)
when - 서비스의 특정 함수가 호출되었을 때
then - 해당하는 형식을 리턴하는지 (assertThat)
해당하는 레포지토리의 함수가 호출되었는지 (then(class).should(method)) 를 체크한다.

📌 서비스 레이어에 적합한 테스트


서비스는 외부 요인(서블렛, DB)을 배제하고 테스트할 수 있는 레이어
=> 스프링 컨테이너를 제외하고 테스트하도록 서비스 레이어에 적합한 테스트 필요
=> 유닛 테스트

📌 @ExtendWith


  • 단위 테스트에 공통적으로 사용할 '확장 기능을 선언'해주는 역할
  • 인자로 확장할 Extension을 지정
    -> 주로 SpringExtension.class 또는 MockitoExtension.class를 사용
  • @ExtendWith(SpringExtension.class) - Junit5와 Spring Test Context 프레임워크를 통합해 사용할 때 사용
  • @ExtendWith(MockitoExtension.class) - JUniit5와 Mockito를 연동해 테스트를 진행할 때 사용 

📌 any()


  • any()는 Mockito의 ArgumentMatchers에서 제공하는 메서드
  • Mock 객체의 동작을 정의하거나 검증하는 단계에서 매개변수가 중요하지 않을 때 사용
  • e.g. 
    메서드의 실행만을 확인하고 싶거나
    특정 매개변수를 설정하는게 불필요한 경우
  • ❗주의❗
    any( xxx.class ) 함수는 타입만 지정할 뿐 실제로 리턴하는 것은 null
    즉, any( AccountUser.class ) 는 AccountUser account = null; 과 동일
// 동작 정의에 사용되는 any
given(productRepository.save(any(Product.class)))
	.willReturn(returnsFirstArg());

// 검증에 사용되는 any
then(accountService).should().createAccount(anyLong(), anyLong());

📌 서비스 테스트의 시나리오


  • 초기 세팅
    @Mock을 사용하므로 테스트 클래스 위에 @ExtendWith(MockitoExtension.class) 작성
    repository 객체를 @Mock으로 선언
    service 객체를 @InsertMocks로 선언하여 자동으로 레포지토리를 주입받기
  • given
    레포지토리 객체의 어떤 함수가 어떤 값을 리턴하는지 설정
  • when
    서비스 객체의 메소드 호출
  • then
    서비스의 메소드가 반환한 값이 의도한 값인지를 assertThat 문으로 확인
    의도한 레포지토리의 메소드가 호출되었는지를 then(repository).should(method) 메서드로 확인

📌 서비스 테스트 코드


@ExtendWith(MockitoExtension.class)
class AccountServiceTest {
    @Mock
    private AccountRepository accountRepository;

    @Mock
    private AccountUserRepository accountUserRepository;

    @InjectMocks // 위에서 mock으로 만들어준 객체가 자동으로 주입됨
    private AccountService accountService;

    @DisplayName("최초계좌의 계좌번호는 1000000000")
    @Test
    void createFirstAccount() {
        //given
        //Account 저장에 필요한 AccountUser 객체 - 보통 따로 빼서 작성하기도 함
        //❗모든 객체를 mock으로 만들 수는 없음, NPE를 막기 위한 실물 객체도 필요❗ 
        AccountUser user = AccountUser.builder()
                .name("yonso")
                .id(1L)
                .build();
        
        // 아무런 계좌도 없는 상황을 설정
        given(accountRepository.findFirstByOrderByIdDesc())
                .willReturn(Optional.empty()); 
        
        // save 함수 stub
        given(accountRepository.save(any(Account.class)))
                .willReturn(Account.builder()
                                   .accountUser(user)
                                   .build());

        ArgumentCaptor<Account> captor = ArgumentCaptor.forClass(Account.class);

        //when
        AccountDto accountDto = accountService.createAccount(1L, 1000L);

        //then
        verify(accountRepository, times(1)).save(captor.capture());
        Assertions.assertThat(captor.getValue().getAccountNumber())
                .isEqualTo("1000000000");
    }

📌 argumentCapture


  • 단위 테스트는 타깃이 되는 객체만을 철저히 검증하는 테스트
    -> 따라서 최대한 의존하고 있는 객체에 영향을 받지 않아야 함
  • 이를 위해, 메소드의 인자로 들어가는 값을 중간에서 검증하고 싶은 경우가 생길 수 있음
  • 위 코드에서는 repository.save()를 Mock으로 객체로 만들고, stub을 했으나,
    그 전에 save의 인자로 들어오는 값을 검증하고 싶은 상황
  • 이때 ArgumentCatptor를 활용하면 메소드에 들어가는 인자를 중간에 가로채어 테스트를 진행할 수 있음

 

  • 선언 방법
    ArgumentCaptor<T> {captor_이름} = ArgumentCaptor.forClass(T.class);
  • 사용 방법
    verify({타겟_인스턴스}).{타겟_메서드}({정의한_ArgumentCaptor}.capture());
  • 검증 방법
    {정의한_ArgumentCaptor}.getValue();

참고 : [Junit5] 메소드 인자 값을 확인하고 싶을 때 - ArgumentCeptor :: Orca's Develop Blog (tistory.com)

📌 Mock 객체를 stub하지 않는다면? 


  • Stub하지 않은 메소드들의 경우 모키토의 기본 전략인 Answers.RETURNS_DEFAULTS에 따라
    타입 별로 디폴트 값을 리턴한다.
  • e.g. int -> 0 / class -> null

출처 :&nbsp;https://wesome.org/mockito-3-answere-returns-defaults

 

💡 서비스 레이어 테스트 참고 (정말 잘 정리된 블로그) :  [Spring] JUnit과 Mockito 기반의 Spring 단위 테스트 코드 작성법 (3/3) - MangKyu's Diary (tistory.com)

 

[Spring] JUnit과 Mockito 기반의 Spring 단위 테스트 코드 작성법 (3/3)

이번에는 Spring 기반의 웹 애플리케이션에서 테스트를 작성하는 방법에 대해 알아보도록 하겠습니다. 1. Mockito 소개 및 사용법 [ Mockito란? ] Mockito는 개발자가 동작을 직접 제어할 수 있는 가짜 객

mangkyu.tistory.com