테스트 코드 작성법(Mockito)

이수찬·2023년 3월 31일
0

Mockito란 가짜 객체를 만들어 실제 객체의 행동을 지정해 테스트 할 수 있는 방법을 제공해주는 라이브러리이다.

Mockito또한 Junit5와 같은 테스트 기반 프레임워크이며,
기존에 @RunWith(MockitoJUnitRunner.class)로 Junit과 Mockito를 연동했다면, SpringBoot 2.2.0 부터는 공식적으로 JUnit5를 지원함에 따라, @ExpendWith(MockitoExtension.class)를 사용해야 결합이 가능하다.

실제 계좌 생성 코드를 통해 Mockito를 활용한 단위테스트를 작성해보자.

  1. service test

[account entity]

@Getter
@NoArgsConstructor
@EntityListeners(AuditingEntityListener.class)
@Entity
public class Account {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true, nullable = false, length = 20)
    private Long number; // 계좌번호

    @Column(nullable = false, length = 4)
    private Long password; // 계좌비밀번호

    @Column(nullable = false)
    private Long balance; // 잔액 (디폴트 값 1000원)

    @ManyToOne(fetch = FetchType.LAZY)
    private User user;

    @LastModifiedDate
    @Column(nullable = false)
    private LocalDateTime updatedAt;

    @CreatedDate
    @Column(nullable = false)
    private LocalDateTime createdAt;
}

[account service 코드]
아래는 사용자의 계좌등록을 하는 비즈니스 로직이다.

@RequiredArgsConstructor
@Transactional(readOnly = true)
@Service
public class AccountService {
    private final Logger log = LoggerFactory.getLogger(getClass());
    private final UserRepository userRepository;
    private final AccountRepository accountRepository;
    private final TransactionRepository transactionRepository;

    @Transactional
    public AccountSaveRespDto 계좌등록(AccountSaveReqDto accountSaveReqDto, Long userId) {
        User userPS = userRepository.findById(userId).orElseThrow(
                () -> new CustomApiException("유저를 찾을 수 없습니다"));

        Optional<Account> isUseAccountOP = accountRepository.findByNumber(accountSaveReqDto.getNumber());
        if (isUseAccountOP.isPresent()) {
            throw new CustomApiException("해당 계좌가 이미 존재합니다");
        }

        Account accountPS = accountRepository.save(accountSaveReqDto.toEntity(userPS));
        return new AccountSaveRespDto(accountPS);
    }

[account service 테스트]

@ExtendWith(MockitoExtension.class)
public class AccountServiceTest extends DummyObject {
    private final Logger log = LoggerFactory.getLogger(getClass());
    @InjectMocks
    private AccountService accountService;

    @Mock
    private AccountRepository accountRepository;

    @Mock
    private UserRepository userRepository;

    @Mock
    private TransactionRepository transactionRepository;

    @Spy
    private ObjectMapper om;

    @Test
    public void 계좌등록_test() throws Exception {
        // given
        AccountSaveReqDto accountSaveReqDto = new AccountSaveReqDto();
        accountSaveReqDto.setNumber(1111L);
        accountSaveReqDto.setPassword(1234L);

        // stub 1
        User ssar = newMockUser(1L, "ssar", "쌀");
        when(userRepository.findById(ssar.getId())).thenReturn(Optional.of(ssar));

        // stub 2
        // Account isUseAccount = newMockAccount(1L, 1111L, 1000L, ssar);
        when(accountRepository.findByNumber(1111L)).thenReturn(Optional.empty());

        // stub 3
        Account ssarAccount = newMockAccount(1L, 1111L, 1000L, ssar);
        when(accountRepository.save(any())).thenReturn(ssarAccount);

        // when
        AccountSaveRespDto accountSaveRespDto = accountService.계좌등록(accountSaveReqDto, ssar.getId());
        String responseBody = om.writeValueAsString(accountSaveRespDto);
        log.debug("테스트 : " + responseBody);

        // then
        assertThat(accountSaveRespDto.getNumber()).isEqualTo(1111L);
    }
  1. 사용자를 조회하는 부분
  2. 계좌번호를 조회하는 부분
  3. 계좌DB에 신규 계좌를 등록하는 부분

위의 3가지 코드들은 모두 repository가 수행하는 부분이다.
즉, service 코드를 test할때 repository의 코드는 테스트할 필요가 없다.

우리는 service 코드를 테스트 하는 것이기에 repository의 코드는 테스트하지 않는다.

기존의 springbootTest에서는 repository 역시 스프링컨테이너에 빈으로 등록하여, 해당 메서드들을 실행하게 되는데, 이러면 테스트에 불필요한 의존성이 추가되며, spring 기반으로 테스트가 진행되기에 spring 컨테이너를 띄우는데 오랜 시간이 걸린다.
=> 단위 테스트의 어려움.

그렇기에 테스트하지 않는 부분의 의존성은 mock을 사용하여 가짜 객체로 만든 후 service 메서드에서 가짜 객체의 행동을 정의하여 테스트를 수행하면 된다.

  1. 스프링 프레임워크의 도움을 빌려야 하는 repository 코드는
    @Mock를 사용하여, 가짜 객체를 만들어 준다.
  2. @Mock만으로 만들어진 가짜 객체의 경우, default가 null이기 때문에,
    @ExtendWith(MockitoExtension.class)로 Mockito를 사용한다고 명시해야 한다.
  3. service 코드에서 해당 가짜 객체들을 사용하기 때문에 @InjectMocks를 통해 가짜 객체를 주입해준다.
  4. test 코드에서는 stubbing을 사용하여 가짜 객체들의 행동을 정의해준다.
  5. assertThat을 이용하여 실제 결과와 예상 결과를 비교해준다.

stubbing하는 코드를 좀 더 자세히 살펴보자.

  1. when : when은 mocking된 가짜 객체의 행동을 수행하면
  2. thenReturn : 이러한 결과가 나온다.

stub3 같은 경우에 any()를 사용했는데, save메서드의 매개변수에 어떤 형태의 인자든 모두 올 수 있다는 의미이다.
anyLong()등과 같은 argumentmatcher를 사용하여 매개변수 타입을 지정해 줄 수 있다.

0개의 댓글