컨셉이 정말 재밌는 프레임 워크다 😆
모킹?목킹?마킹?
맛있는 모킹 프레임워크!
사이트에 가보면 숙취 없는 맛이 정말 좋은 모킹 프레임워크라고 소개하고있다.
🙈🍸
근데 모킹이 뭐지;
모르는게 너무 많다 ㅋㅋ 🙈
세상에 🙊
조롱이란 뜻이었다.
디비를 조롱하는건가.........??!?!?
놀라워서 앵무새한테 번역을 부탁해봤더니 흉내내는 이라는 뜻도 있었다.
여기서 사용된 모킹은 흉내내는 이라는 의미겠지!
모의 객체 Mock Object란
주로 객체 지향 프로그래밍으로 개발한 프로그램을 테스트 할 경우
테스트를 수행 할 모듈과 연결되는 외부의 다른 서비스나 모듈들을
실제 사용하는 모듈을 사용하지 않고
실제의 모듈을 흉내내는 가짜 모듈을 작성하여 테스트의 효용성을 높이는데 사용
하는 객체이다.
사용자 인터페이스나 데이터베이스 테스트 등과 같이
자동화된 테스트를 수행하기 어려운 때 사용된다.
모의 객체를 생성.
예를 들면
List mockedList = mock();
이라고 작성할 경우
List 자료형인 Mock 객체를 만들어준다.
목 객체의 메서드가 호출될 때 반환될 값을 지정.
예를 들면
when(list.get(0)).thenReturn("하나");
라고 작성할 경우
list.get(0)
호출 시 하나
를 반환!
BDD 스타일 ( Behavior Driven Development )의
테스트를 지원하기 위해 제공하는 메서드이다.
BDD 스타일은
테스트 케이스를 주어진 상황given
에서 ,
어떤 행동을 할 때 when
,
기대하는 결과는 이렇다 then
라는 형식으로 작성하는것을 권장한다.
Mockito에서 given()은
주로 when() 대신 사용되며, 동일한 기능을 수행하지만
테스트 케이스를 읽고 이해하기 더 쉽다.
예를 들면
given(list.get(0)).willReturn("하나");
라고 작성할 경우
list.get(0)
호출 시 하나
를 반환!
특정 메서드가 호출 되었는지 검증.
예를 들면
verify(list).get(0)
이라고 작성할 경우
list.get(0)이 1번 호출 되었는지에 대해 검증한다.
times(int 횟수)
메서드 사용!verify(list, times(10)).get(0)
: list.get(0) 이 10번 호출되었는지 검증작성하지 않은 동작을 검증하려고 하면 아래와 같이 에러가 난다.
"Comparison Failure: <클릭하여 차이점 확인>" 누르면 자세히 알려줌!
요렇게👇
메서드 호출 시 전달되는 인자의 타입을 지정할 수 있게 해주는 기능이다.
Argument matchers에 해당하는 주요 메서드는 다음과 같다.
각각 임의의 int, long, double, String, Object에 매칭
// 값 생성
given(list.get(0)).willReturn("one");
given(list.get(1)).willReturn("two");
given(list.get(anyInt())).willReturn("🙈");
// 값 확인
System.out.println(list.get(0));
System.out.println(list.get(1));
특정 값과 일치하는 인자에 매칭.
주어진 조건
을 만족하는 인자에 매칭
@Test
void listTest2() {
// 값 생성
given(
list.get(
Optional.ofNullable(
argThat(new ArgumentMatcher<Integer>() {
@Override
public boolean matches(Integer argument) {
return argument >= 5;
}
})
).orElse(0)
)
).willReturn("🙈");
// 값 확인
System.out.println(list.get(5));
System.out.println(list.get(1));
System.out.println(list.get(123));
}
argThat을 Optional.ofNullable로 감싼 이유는 자꾸 null이 나와서이다. ㅠㅠ
왜지.. 왜 null이 나올까..... 원인을 알아내지 못함 ㅠㅠ
각각 임의의 List<T>, Set<T>, Map<K, V>에 매칭
실제 객체를 감싸는 모의 객체를 생성.
이를 partial mocking
이라고도 함!
실제 객체의 일부 메소드만 모의화 하거나 오버라이드 하려는 경우에 유용하다.
spy를 사용하면 실제 객체의 모든 메소드는 실제로 동작하지만,
필요한 경우 특정 메서드의 동작을 변경하거나
메서드 호출을 검증할 수 있다.
주의할 점은
when()과 같이 stubbing 할 때 실제 메소드가 호출된다는 것이다.
만약 stubbing 하려는 메서드가 사이드 이펙트를 가지거나
특정 조건에서만 동작한다면
willReturn().given()
구문을 사용하는 것이 좋다.
doReturn().when() 구문을 사용하면,
메서드가 실제로 호출되지 않는다.
// 실제 객체 생성
List<String> aList = new LinkedList<>();
// 실제 객체를 wrapping 하는 spy 객체 생성
List<String> spyList = spy(aList);
// 이 다음 문장인 add 안 될 경우 size()를 임의로 늘려주면 됨
// given(spyList.size()).willReturn(100);
// 실제 메서드 호출
spyList.add("하하");
spyList.add("우하하");
System.out.println(spyList.get(0)); // 하하
System.out.println(spyList.get(1)); // 우하하
System.out.println(spyList.size()); // 2
// spy 객체의 메서드 동작 변경
given(spyList.get(1)).willReturn("우하핳");
System.out.println(spyList.get(1)); // 우하핳
// doReturn 사용 시 get(0) 메서드는 실제로 호출되지 않음
willReturn("HaHa").given(spyList).get(0);
System.out.println(spyList.get(0)); // HaHa
verify(spyList, times(2)).get(0);
verify(spyList).size();
반환값이 없어 when().thenReturn()을 사용할 수 없는
void 메서드를 스텁하는 경우나
* 테스트 스텁( Test stub, 이하 Stub )이란
테스트 중인 모듈이 호출하는 다른 소프트웨어 구성 요소(예: 모듈, 변수, 객체)를
일시적으로 대체하는 소프트웨어 구성 요소를 말한다.
정보 제공: https://blog.naver.com/suresofttech/221180956096
실제 메서드 호출을 회피하고 싶을 때
사용한다.
왜냐하면 메서드를 실제로 실행하지 않고 지정한 결과를 바로 반환하기 때문이다.
실제 메서드 호출에 따른 부작용을 피하고자 할 때 유용하다.
verify(mock, times(5)).someMethod("was called five times");
와 같이 사용하여
특정 메서드가 특정 횟수만큼 호출되었는지 검증할 수 있다.
Verification modes에 해당하는 주요 메서드는 다음과 같다.
특정 메서드가 n번 호출되었음을 검증한다.
verify(list, times(1)).get(0);
특정 메서드가 한 번도 호출되지 않았음을 검증한다.
verify(list, never()).get(0);
특정 메서드가 최소 n번 호출되었음을 검증한다.
verify(list, atLeast(1)).get(0);
특정 메서드가 최대 n번 호출되었음을 검증한다.
verify(list, atMost(1)).get(0);
특정 메서드가 한 번 호출되었고,
다른 메소드는 호출되지 않았음을 검증한다.
verify(list, only()).get(0);
여러 메서드 호출이 특정 순서대로 이루어졌음을 검증한다.
// 비교할 Mock 객체 2개 생성
List<String> first = mock();
List<String> second = mock();
first.add("하이");
second.add("Hi");
InOrder inOrder = inOrder(first, second); // 순서를 검증할 목 객체 지정
inOrder.verify(first).add("하이");
inOrder.verify(second).add("Hi");
// 비교할 Mock 객체 2개 생성
List<String> first = mock();
List<String> second = mock();
first.add("하이");
second.add("Hi");
InOrder inOrder = inOrder(first, second); // 순서를 검증할 목 객체 지정
inOrder.verify(second).add("Hi");
inOrder.verify(first).add("하이");
db를 모방한 간단한 테스트 실습
인데 너무 어려웠던 것 ㅠㅠ
아직 repository / service 구조가 어색해서 더 어렵게 느껴졌던 것 같다.
dependencies {
// mockito ----------------------------------------------------
testImplementation("org.mockito:mockito-core:4.11.0+")
testImplementation("org.mockito:mockito-junit-jupiter:4.11.0")
}
import lombok.*;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
private int id;
private String name;
private String email;
}
import java.util.Optional;
public interface UserRepository {
Optional<User> findById(int id);
}
public class UserService {
private UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUserById(int id) {
return userRepository
.findById(id)
.orElseThrow(
() -> new RuntimeException("User not found")
);
}
}
@DisplayName("Service Test")
@ExtendWith(MockitoExtension.class)
@TestInstance(TestInstance.Lifecycle.PER_METHOD)
public class MockitoDBTest {
@Mock // UserRepository 타입의 모의 객체 생성
UserRepository userRepository;
@InjectMocks // 모의 객체 주입
UserService userService;
@Test
void insert() {
}
}
테스트 클래스에 붙이는 이름
@ExtendWith
: 사용자 정의 확장을 등록하는 어노테이션
MockitoExtension.class
: Mockito 라이브러리에서 제공하는 JUnit 5 확장.
MockitoExtension.class
를 사용하면
Mockito의 다양한 기능을 JUnit 5 테스트와 쉽게 통합할 수 있다.
MockitoExtension.class
에서 제공하는
@Mock
, @InjectMocks
, @Spy
등의 어노테이션을 사용하여
Mockito의 모의 객체를 생성하고 주입할 수 있다.
테스트 클래스의 생명주기를 설정하는 어노테이션이다.
TestInstance.Lifecycle.PER_METHOD
@TestInstance()
의 기본 값은 TestInstance.Lifecycle.PER_METHOD
로,
테스트 생명주기를 메서드 단위로 설정한다는 의미이다.
이 기본값으로 인해
테스트 클래스를 실행하면
클래스 안의 메서드들이 순서 없이 임의적으로 실행된다.
TestInstance.Lifecycle.PER_METHOD
설정을 유지하면( 기본값 )
@BeforAll
, @AfterAll
어노테이션이 붙은 메서드를 static
으로 작성해야 한다.
또한 메서드간에 테스트 인스턴스를 공유할 수 없어 ,
각각 안정적으로 독립적인 테스트가 가능해진다.
TestInstance.Lifecycle.PER_CLASS
TestInstance.Lifecycle.PER_CLASS
설정은
테스트 생명주기를 메서드 단위가 아닌, 클래스 단위로 설정하는 것이 된다.
이러한 설정을 한다면,
한 클래스 안의 메서드들이 테스트 인스턴스를 공유할 수 있게 되고,
@BeforeAll
, @AfterAll
어노테이션이 붙은 메서드를 non-static
으로 관리할 수 있다.
하지만,
테스트 인스턴스를 모든 메서드가 공유하게 된다면
테스트가 서로에게 영향을 미치게 되므로 테스트 독립성이 손상될 수 있기 때문에
주의해서 사용해야 한다.
이 어노테이션이 붙은 객체를 모의 객체로 만들어준다.
이 어노테이션이 붙은 객체에 모의 객체를 주입해준다.
@Test
void insert() {
User user = new User(1, "몽키", "monkey@zh.kim");
given( userRepository.findById(1) ).willReturn( Optional.of(user) );
User result = userService.getUserById(1);
assertEquals(user, result);
verify( userRepository, times(1) ).findById(1);
System.out.println(result.toString());
}
User user = new User(1, "몽키", "monkey@zh.kim");
given( userRepository.findById(1) ).willReturn( Optional.of(user) );
User result = userService.getUserById(1);
assertEquals(user, result);
verify( userRepository, times(1) ).findById(1);
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.sql.*;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@DisplayName("Service Test")
@ExtendWith(MockitoExtension.class)
@TestInstance(TestInstance.Lifecycle.PER_Method)
public class MockitoDBTest {
@Mock // UserRepository 타입의 모의 객체 생성
UserRepository userRepository;
@InjectMocks // 모의 객체 주입
UserService userService;
@Test
void insert() {
User user = new User(1, "몽키", "monkey@zh.kim");
given(userRepository.findById(1)).willReturn(Optional.of(user));
User result = userService.getUserById(1);
assertEquals(user, result);
verify(userRepository, times(1)).findById(1);
System.out.println(result.toString());
}
@Test
void insert2() {
User user = new User(2, "코알라", "native.bear@zh.kim");
given(userRepository.findById(1)).willReturn(Optional.of(user));
User result = userService.getUserById(1);
assertEquals(user, result);
verify(userRepository, times(1)).findById(1);
System.out.println(result.toString());
}
}