해당 Annotation들이 동작하게 하려면, @ExtendWith(MockitoExtension.class) 를 사용하거나, MockitoAnnotations.openMocks(this) 를 적용해야 함 (근데 이렇게 Annotation 기반으로 Mock 주입시, 실제 BDDMocktio.mock 으로 내가 직접 만들어서(43ms) 넣어주는거에 비해 7배(283ms)나 느림
Spring Boot에서 지원해주는 어노테이션
@MockBean : ApplicationContext 안에 있는 bean 의 Mockito mock을 정의하기 위해 사용 (해당 Mock이 ApplicationContext에 존재하는 Bean이라면 @MockBean 아니라면 @Mock 사용)
BDDMockito provides BDD aliases for various Mockito methods, so we can write our Arrange step using given (instead of when), likewise, we could write our Assert step using then (instead of verify) - Quick Guide to BDDMockito(Baeldung)
ArgumentCaptor : method에 넘겨지는 인자를 잡아낼 수 있음. 우리가 테스트하고자 하는 메서드 안에서 넘겨지는 인자에 접근하지 못할 때 유용하게 사용이 가능함ArgumentCaptor를 사용할 때는 stubbing으로 사용하지말라. 테스트 가독성을 떨어뜨린다 (당연한 말인것 같음)
ArgumentMatcher : 메서드에 어떤 인자가 넘겨지는지를 좀 더 넓게 명시하거나 모르는 값에 대해서 반응하도록 Mock을 구성하고 싶을 때 사용함
ArgumentMatcher를 이용할 때는 메서드의 모든 인자에 대해 적용하든지, 아니면 아예 적용을 하지 않아야 함
CustomArgumentMatcher
CustomArgumentMatcher 🆚 ArgumentCaptor
ArgumentCaptor를 쓸 때는 assert를 하고자 할 때. 해당 메서드에 넘겨진 인자에 대해서 assertion을 하고자 할 때 사용하기 적절함
CustomArgumentMatcher는 stubbing으로 사용하고자 할 때 좋다. 위에서 ArgumentCaptor를 stubbing에 쓰지 말라고 한 것에 반해, ArgumentMatcher 는 stubbing에 좋음
//using mock
mockedList.add("once");
mockedList.add("twice");
mockedList.add("twice");
mockedList.add("three times");
mockedList.add("three times");
mockedList.add("three times");
//following two verifications work exactly the same - times(1) is used by default
verify(mockedList).add("once");
verify(mockedList, times(1)).add("once");
//exact number of invocations verification
verify(mockedList, times(2)).add("twice");
verify(mockedList, times(3)).add("three times");
//verification using never(). never() is an alias to times(0)
verify(mockedList, never()).add("never happened");
//verification using atLeast()/atMost()
verify(mockedList, atMostOnce()).add("once");
verify(mockedList, atLeastOnce()).add("three times");
verify(mockedList, atLeast(2)).add("three times");
verify(mockedList, atMost(5)).add("three times");
// stub를 활용한 Mocking. 상태 검증
class OrderRepositoryStub implements OrderRepository{
@Override
public Order insert(Order order) {
return null;
}
}
@Test
@DisplayName("오더가 생성돼야 한다. (stub)")
void createOrder() {
// Given
MemoryVoucherRepository voucherRepository = new MemoryVoucherRepository();
Voucher fixedAmountVoucher = new FixedAmountVoucher(UUID.randomUUID(), 100L);
voucherRepository.insert(fixedAmountVoucher);
var sut = new OrderService(new VoucherService(voucherRepository), new OrderRepositoryStub());
// When
var order = sut.createOrder(UUID.randomUUID(), List.of(new OrderItem(UUID.randomUUID(), 200, 1)),
fixedAmountVoucher.getVoucherId());
// Then
assertThat(order.totalAmount(), is(100L));
assertThat(order.getVoucher().isEmpty(), is(false));
assertThat(order.getVoucher().get().getVoucherId(), is(fixedAmountVoucher.getVoucherId()));
assertThat(order.getOrderStatus(), is(OrderStatus.ACCEPTED));
}
// Mock 객체를 활용한 모킹. 행위 검증
@Test
@DisplayName("오더가 생성돼야 한다. (mock)")
void createOrderByMock() {
// Given
var voucherServiceMock = Mockito.mock(VoucherService.class);
Voucher fixedAmountVoucher = new FixedAmountVoucher(UUID.randomUUID(), 100L);
var orderRepositoryMock = Mockito.mock(OrderRepository.class);
Mockito.when(voucherServiceMock.getVoucher(fixedAmountVoucher.getVoucherId())).thenReturn(fixedAmountVoucher);
var sut = new OrderService(voucherServiceMock, orderRepositoryMock);
// 이렇게 정의한 부분만 리턴한 대로 값을 반환함. 정의 안한 부분에 대한 호출은 에러 발생
// When
var order = sut.createOrder(
UUID.randomUUID(),
List.of(new OrderItem(UUID.randomUUID(), 200, 1)),
fixedAmountVoucher.getVoucherId());
// Then
assertThat(order.totalAmount(), is(100L));
assertThat(order.getVoucher().isEmpty(), is(false));
var inOrder = Mockito.inOrder(voucherServiceMock, orderRepositoryMock); // mockito 객체들의 호출 순서를 보장하는 방법(inOrder)
inOrder.verify(voucherServiceMock).getVoucher(fixedAmountVoucher.getVoucherId()); // 해당 메서드가 호출이 됐는지를 검증
inOrder.verify(orderRepositoryMock).insert(order);
inOrder.verify(voucherServiceMock).useVoucher(fixedAmountVoucher);
}
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
private final User user = new User("test-user@gmail.com", "abce12!@", Role.ROLE_USER);
}
Junit5 에서 Mockito 사용하기
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
private final User user = new User("test-user@gmail.com", "abce12!@", Role.ROLE_USER);
private AutoCloseable closeable;
@BeforeEach
public void openMocks() {
closeable = MockitoAnnotations.openMocks(this);
}
@AfterEach
public void releaseMocks() throws Exception {
closeable.close();
}