Mockito 프레임워크
모의 객체 생성, 검증, stub을 지원하는 프레임워크
의존 설정
JUnit과 Mockito는 서로 독립적이기 때문에, 버전 간 의존성은 없기 때문에 최신에 안정된 버전을 선택하면 된다.
https://mvnrepository.com/artifact/org.mockito/mockito-core
모의 객체 생성
Mockito.mock() 메서드를 이용하면 특정 타입의 모의 객체를 생성할 수 있다.
[Mockito.mock() 메서드를 이용한 모의 객체 생성]
public class GameGenMockTest {
@Test
void mockTest() {
GameNumGen genMock = mock(GameNumGen.class);
}
}
public interface GameNumGen {
String generate(GameLevel level);
}
- Mockito.mock() 메서드는 클래스, 인터페이스, 추상 클래스에 대해 모의 객체를 생성할 수 있다.
스텁 설정
모의 객체를 생성한 뒤, BDDMockito 클래스를 이용해서 모의 객체에 stub을 구성할 수 있다.
- stub: 구현을 단순한 것으로 대체
[BDDMockito.given()을 이용한 stub 설정]
public class GameGenMockTest {
@Test
void mockTest() {
GameNumGen genMock = mock(GameNumGen.class);
given(genMock.generate(GameLevel.EASY)).willReturn("123");
String genNum = genMock.generate(GameLevel.EASY);
assertEquals("123", genNum);
}
}
- BDDMockito클래스의 given() 메서드는 stub을 정의할 mock객체의 메서드 호출을 전달
- willReturn() 메서드는 stub을 정의한 메서드가 리턴할 값을 지정
[특정 타입의 exception을 발생하도록 stub 설정]
@Test
void mockThrowTest() {
GameNumGen genMock = mock(GameNumGen.class);
given(genMock.generate(null)).willThrow(IllegalArgumentException.class);
assertThrows(IllegalArgumentException.class, () -> {
genMock.generate(null);
});
}
- willThrow() 메서드를 이용하여 stub을 정의한 메서드가 exception을 발생하게 설정
[리턴 타입이 void인 메서드에 대해 exception을 발생하도록 stub 설정]
public class VoidMethodStubTest {
@Test
void voidMethodWillThrowTest() {
List<String> mockList = mock(List.class);
willThrow(UnsupportedOperationException.class)
.given(mockList)
.clear();
assertThrows(UnsupportedOperationException.class, mockList::clear);
}
}
- BDDMockito.willThrow() 메서드는 발생할 exception 타입이나 객체를 인자로 받고,
이어서 given() 메서드에서는 mock 객체의 메서드 실행이 아닌, mock 객체를 전달받는다. - given() 메서드는 mock 객체 자신을 리턴하고, 이때 exception을 발생하는 메서드( clear() )를 호출한다.
- clear()를 호출한다고 실제로 호출되지 않고, exception을 발생할 mock 객체의 메서드를 설정한다.
인자 매칭 처리
org.mockito.ArgumentMatchers 클래스를 통해 stub으로 정의된 메서드의 인자 값을
정확하게 일치하는 값 대신 임의의 값에 일치하도록 설정할 수 있다.
[ArgumentMatchers.any() 메서드로 인자 값 매칭 처리]
public class AnyMatcherTest {
@Test
void anyMatchTest() {
GameNumGen genMock = mock(GameNumGen.class);
given(genMock.generate(any())).willReturn("456");
String genNum = genMock.generate(GameLevel.EASY);
assertEquals("456", genNum);
String genNum2 = genMock.generate(GameLevel.NORMAL);
assertEquals("456", genNum2);
}
}
- ArgumentMatchers.any() 메서드를 인자 위치에 전달하여 메서드의 인자에 모든 값이 일치하도록 stub을 설정한다.
- generate() 메서드에 어떤 인자가 오든 "456"을 리턴한다.
[stub을 설정한 메서드의 인자가 두개 이상인 경우]
@Test
void invalidUseAnyMatch_Then_ThrowException() {
List<String> mockList = mock(List.class);
given(mockList.set(anyInt(), "123")).willReturn("456");
}
- mockList.set() 메서드의 stub을 설정할 때 첫번째 인자는 ArgumentMatchers.anyInt() 메서드를 이용하고,
두번째 인자는 "123"이라는 특정 값에 일치하도록 설정했다.
위 테스트 코드를 실행하면 아래와 같은 에러가 발생한다.

위 exception은 org.mockito.internal.invocation.MatcherBinders의 validateMatchers()에서 발생한다.

- ArgumentMatchers의 메서드는 내부적으로 ArgumentMatcher로 등록된다.
- Mockito는 한 인자라도 ArgumentMatcher를 사용해서 설정한 경우 위 코드 처럼 모든 인자를
ArgumentMatcher를 이용해서 설정하도록 한다.
위에 exception이 발생한 코드를 올바르게 동작하도록 수정해보면 아래와 같다.
[임의 값 일치와 정확한 값 일치가 필요한 경우 eq() 메서드를 사용]
@Test
void mixAnyAndEq() {
List<String> mockList = mock(List.class);
given(mockList.set(anyInt(), eq("123"))).willReturn("456");
String old = mockList.set(5, "123");
assertEquals("456", old);
}
행위 검증
모의 객체의 역할 중 하나는 실제로 모의 객체가 호출되었는지 검증하는 것이다.
[모의 객체의 메서드의 호출 여부 검증 코드]
public class GameTest {
@Test
void init() {
GameNumGen genMock = mock(GameNumGen.class);
Game game = new Game(genMock);
game.init(GameLevel.EASY); //init()에서 GameNumGen의 generate()를 호출한다고 가정
then(genMock).should().generate(GameLevel.EASY);
}
}
- BDDMockito.then() - 메서드 호출 여부를 검증할 모의 객체를 전달받는다.
- should() - 모의 객체의 메서드가 호출되어야 한다는 것을 설정한다.
- should()메서드 이후 - 실제로 호출될 메서드( generate() )를 지정한다.
[모의 객체의 메서드가 정확히 한 번만 호출되었는지 검증하는 코드]
@Test
void initCalledOnlyOnce() {
GameNumGen genMock = mock(GameNumGen.class);
Game game = new Game(genMock);
game.init(GameLevel.EASY); //init()에서 GameNumGen의 generate()를 호출한다고 가정
then(genMock).should(only()).generate(GameLevel.EASY);
}
- should() 함수에 Mockito.only()를 넘겨주면 설정한 모의 객체의 메서드가 한번만 호출되었는지 검증할 수 있다.
인자 캡쳐
모의 객체의 메서드를 호출할 때 사용한 인자를 검증할 때 사용한다.
[ArgumentCaptor를 이용한 모의 객체 메서드 호출 인자 사용]
public class ArgumentCaptorTest {
private UserRegister userRegister;
private EmailNotifier mockEmailNotifier = mock(EmailNotifier.class);
@Test
void whenRegisterThenSendMail() {
// register()에서 emailNotifier의 sendRegisterEmail() 호출한다고 가정
userRegister.register("id", "pw", "email@email.com");
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
then(mockEmailNotifier)
.should().sendRegisterEmail(captor.capture());
String realEmail = captor.getValue();
assertEquals("email@email.com", realEmail);
}
}
- ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
- String 타입의 인자를 보관할 수 있는 ArgumentCaptor를 생성한다.
- should().sendRegisterEmail(captor.capture());
- 모의 객체의 메서드 호출 여부를 검증하는 코드에서 인자로 위에서 생성한 ArgumentCaptor#captor() 메서드를 전달한다.
- captor.getValue();
- 모의 객체의 메서드를 실행할 때 사용한 인자는 ArgumentCaptor#getValue() 메서드로 구할 수 있다.
JUnit5 확장 설정
Mockito의 JUnit5 의 확장 기능을 사용하면 Mockito.mock()을 이용해서 모의 객체를 생성하는 대신,
Annotation을 통해서 간편하게 생성할 수 있다.
[Mockito JUnit Jupiter 의존성 추가]
testImplementation 'org.mockito:mockito-junit-jupiter:5.9.0'
// https://mvnrepository.com/artifact/org.mockito/mockito-junit-jupiter
[JUnit5 확장 기능을 통한 @Mock 애노테이션으로 모의 객체 생성]
@ExtendWith(MockitoExtension.class)
public class JUnit5ExtensionTest {
@Mock
private GameNumGen gameGen;
@Test
void mockTest() {
assertNotNull(gameGen);
}
}'Spring > 테스트' 카테고리의 다른 글
| [테스트 주도 개발 시작하기] Chap05 JUnit5 기초 (3) | 2024.09.03 |
|---|---|
| [테스트 주도 개발 시작하기] Chap04 TDD-기능 명세-설계 (0) | 2024.09.01 |
| [테스트 주도 개발 시작하기] Chap03 테스트 코드 작성 순서 (1) | 2024.09.01 |
| [테스트 주도 개발 시작하기] Chap02 TDD (0) | 2024.08.29 |