들어가기 앞서, 개발에서 중요한 2가지.
1. 왜 이렇게 작성했는지를 아는 것 : 개발이라는 것은 비용 대비 효율성이기 때문에 이유가 필요합니다.
2. 테스트 코드 : 제대로 잘 동작하는지, 빈틈은 없는지, 오류가 나지 않는지 확인하는게 중요합니다.
※ 단위 테스트 VS 통합 테스트
- 통합 테스트란 실제 환경에서 실행하여 여러 클래스와 메소드가 정상적으로 잘 작동하는지를 테스트하는 것.
- 단위 테스트란 각각의 클래스와 메소드가 개별적으로(독립적으로) 잘 작동하는지를 테스트 하는 것.
(다른 코드와 독립적으로 만드는게 복잡함 -> Mockito를 사용)
01. Mockito란?
Mockito란 단위 테스트를 쉽게 작성할 수 있도록 도와주는 Mocking 프레임워크입니다.
mock이라는 뜻은 가짜, 가상이라는 뜻인데, 테스트를 위한 가상의 환경을 만들어주는 역할을 합니다.
쉽게 말해 특정 클래스나 메소드가 잘 작동하는지 확인하기 위해 영향을 주는 요인들을
mockito를 통해 가상의 값들로 만드는 것입니다.
02.어떻게?
바로 Mock객체를 만드는 것입니다.
Mock객체는 실제 객체처럼 동작은 하지만, 내부적으로 아무 것도 없는 가짜 객체입니다.
이를 활용해서 독립적인 환경을 만들 수 있습니다.
@Test
@Order(5)
@DisplayName("계좌 입금")
void deposit() {
Mockito.when(accountRepository.deposit(any(),anyLong(),anyLong())).thenReturn(1);
Mockito.when(accountRepository.countByAccountNumber(any(),anyLong())).thenReturn(1);
boolean result = bankService.depositAccount(null,Long.MAX_VALUE,Long.MAX_VALUE);
assertTrue(result);
Mockito.verify(accountRepository,Mockito.times(1)).deposit(any(),anyLong(),anyLong());
}
@Test
@Order(6)
@DisplayName("계좌 입금 - 계좌가 존재하지 않을 때")
void deposit_account_not_found() {
Mockito.when(accountRepository.countByAccountNumber(any(),anyLong())).thenReturn(0);
assertThrows(AccountNotFoundException.class,()-> bankService.depositAccount(null,Long.MAX_VALUE,Long.MAX_VALUE));
Mockito.verify(accountRepository,Mockito.times(1)).countByAccountNumber(any(),anyLong());
}
@Test
@Order(7)
@DisplayName("계좌 출금")
void withdraw() {
Mockito.when(accountRepository.countByAccountNumber(any(),anyLong())).thenReturn(1);
Mockito.when(accountRepository.findByAccountNumber(any(),anyLong())).thenReturn(Optional.of(new Account(1l,"nhn아카데미",10_0000l)));
Mockito.when(accountRepository.withdraw(any(),anyLong(),anyLong())).thenReturn(1);
boolean result = bankService.withdrawAccount(null,Long.MAX_VALUE,1_0000);
assertEquals(true,result);
}
@Test
@Order(8)
@DisplayName("계좌 출금 - 계좌가 존재하지 않을 떄")
void withdraw_account_not_found(){
Mockito.when(accountRepository.countByAccountNumber(any(),anyLong())).thenReturn(0);
assertThrows(AccountNotFoundException.class,()->bankService.withdrawAccount(null,Long.MAX_VALUE,Long.MAX_VALUE));
Mockito.verify(accountRepository,Mockito.times(1)).countByAccountNumber(any(),anyLong());
}
@Test
@Order(9)
@DisplayName("계좌 출금 - 잔액부족")
void withdraw_balacne_not_enough(){
Mockito.when(accountRepository.countByAccountNumber(any(),anyLong())).thenReturn(1);
Mockito.when(accountRepository.findByAccountNumber(any(),anyLong())).thenReturn(Optional.of(new Account(1l,"nhn아카데미",10_0000l)));
Assertions.assertThrows(BalanceNotEnoughException.class,()-> bankService.withdrawAccount(null,1l, 20_0000l));
Mockito.verify(accountRepository,Mockito.times(1)).findByAccountNumber(any(),anyLong());
}
하지만 위에처럼 바로 사용은 불가능하다.
Mock객체를 아래와같이 미리 만들어서 선언해줘야한다
@Mock
AccountRepositoryImpl accountRepository;
03. Mockito의 수행과정 정리
일반적인 TEST코드에서의 수행 과정
- Given : 사전 조건
- When : 테스트할 메소드 호출
- Then : 검증
// GIVEN
Item item = new Item("A",10000,10);
// WHEN
Item saveItem = itemRepository.save(item);
// THEN
Item findItem = itemRepository.findByid(item.getId());
assertThat(findItem).isEqualTo(saveItem);
비슷한 구조의 Mockito 수행과정
- 모의 객체 생성 : Mock
- 예상 호출되는 메서드 동작 설정 : Stub
- 메서드 호출 검증 : verify
@Test
@DisplayName("기본 구조 예시")
void testCode(){
// 모의 객체 생성 : Mock
Repository mockRepo = Mockito.mock(Repository.class);
// 예상 호출 메서드 설정 : Stub
Mockito.when(mockRepo.countNumber()).thenReturn(5);
System.out.println(mockRepo.countNumber());
// 메서드 호출 검정 : Verify
Mockito.verify(mockRepo).countNumber();
}
04. Mockito가 왜 중요할까?
★독립적이다 == Mockito의 핵심.
단위 테스트를 위해서는 해당 클래스나 메소드를 제외한 외부 영향으로 부터 벗어나야하는데
이를 아주 간편하게 도와주는게 바로 Mockito입니다.
그 덕분에 어느 환경에서든 독립적인 환경에서 테스트를 할 수 있는것입니다.
05. 추가적인 개념들.
- Mockito에는 Mock / Stub / verify / ( spy )
05-1. SPY란
- 스파이라는 말이 우리가 흔히 알고 있는 스파이랑 비슷한 개념인데 겉으로는 평범한 사람이지만 실제로는 첩보원인 것처럼, 실제 객체를 사용하면서 특정 동작에 대해서는 가상으로 사용하는 개념을 말합니다.
- 쉽게 말해 mock은 완전 가짜를 말하고 Spy는 실제인 척하는 가짜, 즉 일부만 가짜인 것 입니다.
class BankAccount {
private int balance = 1000;
// 출금
public void withdraw(int amount) {
balance -= amount;
}
// 조회
public int getBalance() {
return balance;
}
}
→ 실제 BackAccount기능은 유지하면서 잔액 조회만 가짜로 만듦.
@Test
void testSpyBankAccount() {
BankAccount spyAccount = spy(new BankAccount());
when(spyAccount.getBalance()).thenReturn(999999999999); // 계좌 잔액 조작!
System.out.println(spyAccount.getBalance()); // 999999999999(실제 X)
}
05-2. Mockito 클래스

- @CheckReturnValue :
- 의미 : 해당 메소드는 반환값을 사용하지 않으면 의미가 없으니 활용하라!
- 반환값에 의미가 있다를 표현하기 위해 사용됩니다. (mock, spy, verify … == 반환값을 기반으로 동작)
- ArgumentMatchers 클래스
- 의미 : 어떤 값이 들어와도 이 동작을 실행해라
- 역할 : Mock 객체의 특정 메서드를 호출할 때 전달되는 인자를 유연하게 검증할 수 있도록 도와주는 기능입니다. 메서드 호출시에 너무 정확한 값이 필요하면 테스트가 복잡해 질 수 있기 때문입니다.
| 메서드 | 설명 |
| any() | 아무 값이나 허용 |
| any(Class<T> type) | 특정 타입의 값 허용 |
| eq(T value) | 특정 값과 정확히 일치하는 경우 허용 |
| argThat(Matcher<T> matcher) | 커스텀 조건을 적용하여 매칭 |
06. Mockito 한계 및 사용
06-1. Mockito의 한계.
- 그럼에도 Mockito에는 한계가 존재하는데
- Static으로 선언된 메소드는 Mocking할 수 없습니다.
- private로 선언된 메소드는 Mocking할 수 없습니다.
- Final 클래스 및 Final 메서드는 Mocking할 수 없습니다
- ... 등등
- 현재 최신버전에서는 위의 제한들이 많이 해결되었습니다.
- static → mockStatic()
- private → spy + Reflection
- final → 최신버전에서는 바로 사용 가능
- 제한들이 없어졌지만 모키토도 적절히 사용할 필요가 있습니다.
06-2. 실제 비즈니스 로직 테스트에는 적합하지 않음
- 비즈니스 로직이 중요한 경우, 실제 객체를 활용하는 것이 더 확실한 경우도 있기 때문에 적합하지 않을 수 있습니다.
- Mockito는 특정 메서드 호출을 시뮬레이션할 뿐, 실제 로직을 실행하지 않으므로 검증이 불완전할 수 있습니다.
- 또한 Mock 객체는 너무 많이 사용하면 유지보수가 어려워질 수 있어 적절히 사용하는 것이 좋습니다.