백기선 님의 인프런 강의 "더 자바, 애플리케이션을 테스트하는 다양한 방법"를 정리한 글입니다.
1. 소개
- Java 계열에서 가장 많이 사용되는 Mock 프레임워크 (50%+)
구성 요소
- Mock: 실제 객체처럼 동작하지만, 프로그래머가 행동을 제어할 수 있는 객체
- Mockito: Mock 객체를 쉽게 만들고, 조작하고, 검증할 수 있는 프레임워크
대체제
- EasyMock
- JMock
2. Mockito 시작하기
Gradle
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
testImplementation 'org.mockito:mockito-core:5.12.0'
testImplementation 'org.mockito:mockito-junit-jupitor:5.12.0'
}
Spring Boot 2.2+
- spring-boot-starter-test에 자동 포함됨
3. 적용
Mock 객체 생성
예제) Mockito.mock()
더보기
MemberService memberService = mock(MemberService.class);
StudyRepository studyRepository = mock(StudyRepository.class);
예제) @Mock
더보기
@ExtendWith(MockitoExtension.class)
class StudyServiceTest {
@Mock MemberService memberService;
@Mock StudyRepository studyRepository;
}
- @ExtendWith(MockitoExtension.class)와 함께 사용
- 필드 또는 메서드 매개변수로 가능
예제) 메소드 매개변수 이용
더보기
@ExtendWith(MockitoExtension.class)
class StudyServiceTest {
@Test
void createStudyService(@Mock MemberService memberService,
@Mock StudyRepository studyRepository) {
StudyService studyService = new StudyService(memberService, studyRepository);
assertNotNull(studyService);
}
}
Mock 객체 동작 정의 (Stubbing)
Stubbing API
기능 | 설명 |
일반 Stubbing |
특정 호출에 대해 값을 리턴하도록 설정
|
예외 던지기 |
호출 시 예외를 던지도록 설정
|
연속 호출 Stubbing |
연속 호출 시 각각 다른 값을 리턴하도록 설정
|
void 메서드 예외 던지기 |
void 메서드가 호출될 때 예외를 던지도록 설정
|
void 메서드 실행 없이 무시 |
void 메서드는 기본적으로 아무 일도 하지 않음
|
메서드 호출 시 특정 동작 수행 |
호출 시 사용자 정의 동작 수행 가능
|
호출된 값에 따라 다르게 동작 |
특정 인자에 대해 다르게 반환
|
Argument Matcher 활용 |
인자 매처 (any(), eq(), 등) 사용 가능
|
예제
더보기
@ExtendWith(MockitoExtension.class)
public class MockitoTest {
@Mock
UserService mock;
@Test
void when_thenReturn() {
when(mock.getName()).thenReturn("mockito");
assertEquals(mock.getName(), "mockito");
when(mock.greet("Tom")).thenReturn("Hi Tom");
assertEquals(mock.greet("Tom"), "Hi Tom");
when(mock.greet(anyString())).thenReturn("Hello!");
assertEquals(mock.greet("mockito"), "Hello!");
doAnswer(inv -> {
System.out.println("doAnswer 메서드 호출됨!");
return "doAnswer";
}).when(mock).getName();
assertEquals(mock.getName(), "doAnswer");
}
@Test
void when_ThenReturn_ConsecutiveCall() {
when(mock.getCount()).thenReturn(1, 2, 3);
assertEquals(mock.getCount(), 1);
assertEquals(mock.getCount(), 2);
assertEquals(mock.getCount(), 3);
}
@Test
void when_thenThrow() {
when(mock.getName()).thenThrow(new IllegalStateException("예외 발생!"));
assertThrows(IllegalStateException.class, () -> {mock.getName();});
doThrow(new IllegalStateException()).when(mock).getName();
assertThrows(IllegalStateException.class, () -> {mock.getName();});
}
}
기본 동작 (Mock을 생성했지만 Stubbing하지 않았을 때)
타입 | 기본 반환값 | 설명 |
객체 (reference type) | null |
일반 객체 타입은 기본적으로 null 반환
|
Optional<T> | Optional.empty() |
Optional 타입은 비어 있는 Optional 반환
|
기본형 (int, boolean 등) | 0, false, 0.0f, 0.0d, etc |
기본형 타입은 해당 타입의 기본값 반환
|
컬렉션 (List, Set 등) | 빈 컬렉션 ([], {} 등) |
리스트, 세트 등은 비어 있는 컬렉션 반환
|
void 메서드 | 아무 일도 안 함 |
예외 없이 그냥 실행됨 (noop)
|
예제
더보기
@ExtendWith(MockitoExtension.class)
public class NoStubbingTest {
@Mock
UserService userService;
@Test
void defaultBehavior() {
assertNull(userService.getName());
assertEquals(Optional.empty(), userService.getOptionalName());
assertEquals(0, userService.getAge());
assertFalse(userService.isActive());
assertTrue(userService.getRoles().isEmpty());
userService.logout();
}
}
Mock 객체 사용 검증 (Verify)
기능 | 메서드 / API | 설명 |
호출 횟수 확인
|
verify(mock, times(n)) |
정확히 n번 호출되었는지 확인
|
verify(mock, atLeastOnce()) |
최소 한 번 이상 호출되었는지 확인
|
|
verify(mock, never()) |
전혀 호출되지 않았는지 확인
|
|
호출 순서 확인 | InOrder inOrder = inOrder(...) |
여러 Mock의 메서드가 특정 순서로 호출되었는지 확인
|
호출 타이밍 검증 | verify(mock, timeout(ms)) |
지정한 시간 내에 호출되었는지 확인 (ms 밀리초 단위)
|
불필요한 호출 확인 | verifyNoMoreInteractions(...) |
지정된 호출 외에 다른 호출이 없었는지 확인 (불필요한 호출 확인용)
|
예제
더보기
@Test
void verify_call_times() {
mock.getName();
mock.getName();
verify(mock, times(2)).getName();
verify(mock, atLeastOnce()).getName();
verify(mock, never()).greet("mockito");
}
@Test
void verify_inorder() {
UserService mock1 = mock(UserService.class);
UserService mock2 = mock(UserService.class);
mock1.getName();
mock2.getCount();
InOrder inOrder = inOrder(mock1, mock2);
inOrder.verify(mock1).getName();
inOrder.verify(mock2).getCount();
}
@Test
void verify_timeout() {
new Thread(() -> {
try {
Thread.sleep(50);
mock.getCount();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
verify(mock, timeout(100)).getCount();
}
@Test
void verify_no_more_interactions() {
mock.getCount();
verify(mock).getCount();
verifyNoMoreInteractions(mock);
}
4. BDD Mockito
BDD
- 행위 중심 개발 (Behavior-Driven Development)
- 소프트웨어가 어떻게 행동해야 하는지를 중심으로 개발을 이끌어가는 방법
- 사용자의 관점에서 기능을 정의하고, 그 정의를 바탕으로 테스트를 작성함
핵심 요소
구성 요소 | 설명 |
Story |
사용자 시나리오 중심 설명 (Narrative)
|
Given |
어떤 조건이 주어졌을 때 (상태, 전제조건 등)
|
When |
어떤 행동(이벤트)이 일어나면
|
Then |
어떤 결과가 나와야 한다
|
BDD API
역할 | BDD API | 설명 |
Stubbing (Given)
|
given(mock.method()).willReturn(value) |
특정 호출이 일어날 때 값을 반환하도록 설정
|
given(mock.method()).willThrow(exception) |
호출 시 예외를 던지도록 설정
|
|
willAnswer(answer).given(mock).method(...) |
호출 시 사용자 정의 동작을 실행
|
|
Verification (Then)
|
then(mock).should().method() |
특정 메소드가 호출됐는지 검증
|
then(mock).should(times(n)).method() |
특정 메소드가 n번 호출됐는지 검증
|
|
then(mock).shouldHaveNoMoreInteractions() |
더 이상의 상호작용이 없었는지 검증
|
|
then(mock).should(never()).method() |
특정 메소드가 절대 호출되지 않았는지 검증
|
|
호출 순서 검증
|
InOrder inOrder = inOrder(mock1, mock2) |
여러 mock 간 호출 순서 검증 시작
|
then(mock).should(inOrder).method() |
특정 호출 순서를 따라 호출되었는지 검증
|
|
호출 타이밍 검증 | then(mock).should(timeout(100)).method() |
100ms 이내에 호출되었는지 확인
|
더 이상 호출되지 않음 | then(mock).shouldHaveNoMoreInteractions() |
더 이상의 호출이 없었는지 확인
|
예제
더보기
@Test
void bdd_given_willReturn() {
given(mock.getName()).willReturn("mockito");
assertEquals(mock.getName(), "mockito");
}
@Test
void bdd_given_willThrow() {
given(mock.getName()).willThrow(new IllegalStateException("예외 발생!"));
assertThrows(IllegalStateException.class, () -> {mock.getName();});
}
@Test
void bdd_should() {
mock.getCount();
then(mock).should().getCount();
}
@Test
void bdd_should_times() {
mock.getCount();
mock.getCount();
then(mock).should(times(2)).getCount();
}
@Test
void bdd_should_have_no_more_interactions() {
mock.getCount();
mock.getCount();
then(mock).should(times(2)).getCount();
then(mock).shouldHaveNoMoreInteractions();
}
@Test
void bdd_should_another_method() {
mock.getCount();
then(mock).should(never()).getName();
}
@Test
void bdd_should_order() {
UserService mock1 = mock(UserService.class);
UserService mock2 = mock(UserService.class);
mock1.getName();
mock2.getCount();
InOrder inOrder = inOrder(mock1, mock2);
then(mock1).should(inOrder).getName();
then(mock2).should(inOrder).getCount();
}
@Test
void bdd_should_timeout() {
new Thread(() -> {
try {
Thread.sleep(50);
mock.getCount();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
then(mock).should(timeout(100)).getCount();
}
출처
'Code > Test' 카테고리의 다른 글
[더 자바, 애플리케이션을 테스트하는 다양한 방법] 1. JUnit 5 (0) | 2025.04.19 |
---|---|
[단위 테스트] 9. 목 처리에 대한 모범 사례 (0) | 2025.03.01 |
[단위 테스트] 8-2. 통합 테스트를 하는 이유: 인터페이스 (1) | 2025.03.01 |
[단위 테스트] 8-1. 통합 테스트를 하는 이유: 통합 테스트 (0) | 2025.02.28 |
[단위 테스트] 7-2. 가치 있는 단위 테스트를 위한 리팩터링: 감사 시스템 (0) | 2025.02.28 |