-단위 테스트를 위한 mocking 프레임워크 라고 보면 된다.
public class User {
public User(){}
public User(String username , Integer age){
this.username = username;
this.age = age;
}
private Integer id;
private String username;
private Integer age;
@Service
public class UserService {
private UserRepository userRepository;
public UserService(UserRepository userRepository){
this.userRepository = userRepository;
}
public User save(User user){
return userRepository.save(user);
}
public User findUser(Integer id){
return userRepository.findUser(id)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 회원"));
}
public List<User> findUserList(){
return userRepository.findUserList();
}
위와 같이 User 엔티티가 있고, UserService를 테스트한다고 가정해보자.
@ExtendWith(MockitoExtension.class)
class UserServiceTest{
@Mock
UserRepository userRepository;
@before
void createUserService(UserRepository userRepository){
UserService userService = new UserService(userRepository);
}
}
Mockito를 사용하기 위해서, 먼저 @ExtendWith(MockitoExtension.class)로 확장한다.
@Mock 어노테이션을 이용하여 생성한 UserRepository Mock객체를 이용하여 UserService를 생성한다.
더 간편하게 하기 원한다면,
@before
void createUserService(@Mock UserRepository userRepository){
UserService userService = new UserService(userRepository);
}
더 간단하게,,,??
@Mock
UserRepository userRepository;
@InjectMocks
UserService userService;
이렇게도 사용할 수 있다.
어떻게 생성하는지는 알겠으니 , Mock객체 생성 방법 관련하여 몇가지 어노테이션을 살펴보자.
Mockito에서 Mock(가짜)객체의 의존성 주입을 위해서는 크게 3가지 어노테이션이 있다.
@Mock : Mock 객체를 생성하여 준다.
@Spy : Stub하지 않은 메서드들은 원본 메서드 그대로 사용하는 어노테이션
@InjectMocks : @Mock 또는 @Spy로 생성된 가짜 객체를 주입시켜주는 어노테이션.
@Spy는 무엇인가하는 생각이 든다.
먼저, Mock객체가 하는 행동을 설명한 뒤에 Stub가 무엇인지 설명한 뒤에 알아보자.
Null을 리턴한다.
Primitive 타입은 기본 Primitive값을 리턴한다.
콜렉션은 비어있는 콜렉션
Void메소드는 예외를 던지지 않고, 아무런 일도 발생하지 않는다.
Null을 리턴하는 경우와 void시 예외를 던지지 않는 것만 살펴보자.
@Mock
UserRepository userRepository;
@InjectMocks
UserService userService;
//null을 리턴하는 경우
@Test
void saveTest(){
User user = new User("홍길동",20);
User findUser = userService.save(user);
assertThat(findUser).isNull();
}
//void메소드가 예외를 던지지 않을 경우
@Test
void deleteTest(){
int id = 100;
userService.deleteUser(100);
}
하지만 실제로는 null이 반환되고, 테스트는 통과 된다.
Optional의 경우 Optional.empty가 나온다.
userRepository에는 id값이 존재하지 않는 회원 조회시 , Exception을 던지게 하였다.
하지만 , 아무런 일도 발생하지 않는다.
이러한 결과들이 발생되기 때문에 , Mock객체가 호출되었을때 , 행위들을 직접 지정해줄 수 있는데 이것을
"stubbing" 이라고한다.
@Mock
UserRepository userRepository;
@InjectMocks
UserService userService;
//null을 리턴하는 경우
@Test
void saveTest(){
User user = new User("홍길동",20)
doReturn(user).when(userRepository).save(user);
User findUser = userService.save(user);
assertThat(findUser).isNotNull();
}
//void메소드가 예외를 던지지 않을 경우
@Test
void deleteTest(){
int id = 100;
doThrow(new IllegalArgumentException("존재하지 않는 회원")).when(userRepository).deleteUser(id);
assertThrows(IllegalArgumentException.class , () -> userService.deleteUser(id));
}
위와 같이 mock객체가 호출되었을 때, 어떤 행위를 할것인지 지정해줄 수 있다.
다시 돌아가 @Spy 를 본다면 이해될 것이다.
주의 호출될 때 넘겨주는 인자가 달라질 경우 ,stubbing이 적용되지 않는다.
어떤 인자를 넘기더라도 호출되게끔 하고 싶다면 any() 를 사용하자.
doReturn() : Mock 객체가 특정 값을 반환해야 하는 경우.
doNothing() : Mock 객체가 아무것도 반환하지 않는 경우.
doThrow() : Mock 객체가 예외를 발생시키는 경우.
만일, userRepository의 save()라는 메소드가 진짜 호출 되었는지 확인하려면, 아래처럼 쓸 수 있다.
verify(userRepository , times(1)).save(user);
verify(userRepository , times(1)).save(any());
verify(userRepository , times(1)).save(user1);
verify(userRepository , times(1)).save(user2);
InOrder inOrder = inOrder(memberRepository);
inOrder.verify(memberRepository).save(user1);
inOrder.verify(memberRepository).save(user2);
이 밖에도 많지만 이정도만 적음.