mock vs stub은
행위에 집중하느냐(동작 수행 여부)
상태에 집중하느냐
의 문제이다.
stub 전체 코드
GIVEN, WHEN, THEN의 개별 스텝 별로 어떻게 코드를 작성하는가?
stub은 orderservice에서만 사용하기 때문에
개별 stub을 인라인 클래스로 만들어서 구현할 수도 있지만
memoryVoucher를 이용해서 구현할 것이다.
orderservice 클래스
OrderRepository클래스, VoucherService클래스와 협력관계가 맺어져 있다.
OrderServiceTest클래스
VoucherService에 MemoryVoucherRepository에 대한 stub을 전달해준다.
VoucherService는 생성자 매개변수로 MemoryVoucherRepository를 입력받아야 하는데,
사실 MemoryVoucherRepository자체가 인터페이스이기 때문에 굳이 구현하지 않고도
stub의 기능을 할 수 있다.
stub코드
혹은, MemoryOrderRepository가 없다면 다음과 같이 stub을 만들어줄 수도 있다.
코드
GIVEN은 이처럼 단위 테스트 시 주어진 특정한 상황. 테스트 시의 전제 조건들을 만들어둔 것이다. 이 때 SUT의 생성자에 Mock 객체를 전달해준다
코드
voucherRepository에 fixedAmountVoucher를 넣어주고 준비하는 코드
WHEN은 특정 행위(메서드)를 호출할 때를 의미한다.
이런 상황이 준비되었을 때 어떤 메소드를 호출하는가? 를 생각하면 된다.
Then에서는 만들어진 Order의 상태
에 집중해서 테스트 코드를 작성한다.
Voucher는 Optional이기 때문에 값이 존재할 수도, null일 수도 있다.
하지만 테스트에서는 늘 존재한다고 상정한다.
코드
Optional 클래스의 메소드
- isEmpty()
값이 null이면 True반환
get()
값이 존재하면 return한다.
Mock테스트에는 mokito 라이브러리를 사용한다.
의존성 다이어그램을 보면 알 수 있듯이 mockito 역시 starter 패키지에 의존성이 있기 때문에
starter 패키지만 추가하면 관련 라이브러리가 모두 추가된다. 그래서 따로 pom.xml파일에 의존성을 추가할 필요 없이 mockito를 사용할 수 있다.
import static org.mockito.Mockito.*
jupiter의 assertion 클래스를 static으로 import했던 것처럼
mokito 클래스도 static으로 import하면 편하고 가독성있는 코드를 작성할 수 있다.
mock을 이용한 테스트는 행위에 집중하는 테스트이므로
어떤 메소드들이 호출되어지는지를 제공해야한다.
mock 클래스의 메소드
- mock()
클래스를 mock객체로 만든다.
when()
Mock객체가 감싸고 있는 메소드가 호출되거나 호출했을 때 사용된다.
when 사용 시 Mock객체의 행동(원하는 return값)을 설정할 수 있다.
when은 어떤 동작을할 때~
라고 명시해준다.
~한다
는 명세를 주는 메소드는 다음과 같다.
thenReturn()
thenThrow()
thenAnswer()
thenReturn()
인자 값을 return해준다.
Mock을 사용한 테스트에서 중요한 것은
voucherServiceMock
과 orderRepositoryMock
객체에 대해
어떤 메소드가 정상적으로 호출되는지를 검증해줘야 한다는 것이다.
행위 관점에서 생각해야 한다.
Then코드
- verify()
Mock객체에 대해 원하는 메소드가 특정 조건으로 실행되었는지 검증한다.
Mock객체를 인자로 받는다.
처음 Given에서
fixedAmountVoucher.getVoucherId()
를 하면
fixedAmountVoucher
가 리턴되도록 when()코드를 setup해두었다.
그리고
OrderService
의 실제 테스트 하고자 하는 코드(Order
)를 실행 -> createOrder
를 하게 된다.
Then에서는
만들어진 Order에 대한 상태를 검증할 수도 있고, (assertThat사용)
OrderService 내부에서 사용되는 voucherServiceMock
이 어떤 행동을 하는지,
어떤 메소드가 호출되는지를 검사할 수 있다.
verify(voucherServiceMock).getVoucher((fixedAmountVoucher.getVoucherId())); verify(orderRepositoryMock).insert(order); verify(voucherServiceMock).useVoucher(fixedAmountVoucher);
를 보면
getVoucher()
가 호출되고
orderRepositoryMock
에 order가 추가되고
voucherServiceMock
에 전달한 fixedAmountVoucher
가 사용된다는 것을 검증할 수 있다.
이해를 돕기 위한 createOrder 메소드 코드
이렇게 Mock 객체를 이용해 별도의 코드를 추가하지 않고
메소드 호출 여부 검사, 동작 수행을 이해할 수 있는 Then의 코드를 작성하였다.
그런데, Then의 코드들이 메소드의 특정 순서를 보장하면서 호출되어야 한다고 할 때
mokito에서 이용할 수 있는 메소드가 있다.
inOrder()를 사용한 코드
위 코드는 먼저 voucher
를 사용하고, 그 다음에 voucher
를 가져오려 했기 때문에 에러가 발생한다.
voucher
를 조회하고, 나중에 사용하는 것으로 순서를 바꾸면 정상적으로 잘 작동이 된다.
orderRepositoryMock
의 호출 순서도 알아내고 싶다면
이처럼 inOrder()
를 사용하면 Mock들이 어떤 순서에 따라 호출되어지는지도 판단할 수 있다.
- inOrder()
inOrder()를 사용하면 메소드의 호출 순서를 검증할 수 있다.
inOrder (객체명
)으로 생성한 후 검증하고 싶은 순서대로verify
를 사용한다.
rf
https://greedy0110.tistory.com/57
https://softarchitecture.tistory.com/64
https://effortguy.tistory.com/144