Search
Duplicate
📒

[Spring TDD] 02-1. JUnit과 Mockito 기반의 단위 테스트

상태
완료
수업
Spring TDD
주제
Test
4 more properties
참고

Mockito 소개

NOTE
Mockito ⇒ 개발자가 동작을 직접 제어할 수 있는 가짜 객체를 지원하는 테스트 프레임워크
Mockito 예시) DB에서 실제로 데이터를 읽지않고, 가짜 객체(Mock)으로 만들어서 테스트한다!
Spring 개발은 여러 객체들 간의 의존성이 생기고, 이러한 의존성이 단위 테스트 작성을 어렵게한다.
이러한 의존성이 복잡한 상황을 위해서 가짜 객체를 주입시켜주는 Mockito 라이브러리를 활용할 수 있다!

Mockito 주석 활성화

NOTE
Mockito도 테스팅 프레임워크이기 때문에 JUnit과 결합되기 위해서는 별도의 작업이 필요하다.

1. MockitoJunitRunner 방식(권장)

@ExtendWith(MockitoExtension.class) class Test{}
Java
복사

2. MockitoAnnotations.openMocks()

@BeforeEach void setUp() { MockitoAnnotations.openMocks(this); }
Java
복사

Mockito 사용법

Stubbing

NOTE
Stubbing은 테스트 중에 만들어진 호출에 대해 미리 준비된 답변을 제공하는 것이다!ㅑ

OngoingStubbing 메소드

when에 넣는 메소드의 리턴값을 정의해준다.
// 메소드 형식 Mockito.when([Stubbing할 메소드]).thenReturn(); // 실제 예시 Mockito.when(mockList.size()).thenReturn(100);
Java
복사
메소드명
설명
thenReturn
스터빙한 메소드 호출 후 어떤 객체를 리턴할 건지 정의
thenThrow
스터빙한 메소드 호출 후 어떤 Exception을 Throw할 건지 정의
thenAnswer
스터빙한 메소드 호출 후 어떤 작업을 할지 custom하게 정의mockito javadoc을 보면 이 메소드를 굳이 사용하지 말고 thenReturn, thenThrow 메소드 사용을 추천하고 있습니다.
thenCallRealMethod
실제 메소드 호출

Stubber 메소드

Ongoing과 다르게 when에 Stubbing할 클래스를 넣고 그 후에 메소드를 호출한다.
// 메서드 형식 {Stubber 메소드}.when({스터빙할 클래스}).{스터빙할 메소드} // 불가능 (List가 빈객체이므로 0번째 요소가 없다) when(spy.get(0)).thenReturn("foo"); //위와 같은 경우를 doReturn을 사용한다. doReturn("foo").when(spy).get(0);
Java
복사
메소드명
설명
doReturn
스터빙 메소드 호출 후 어떤 행동을 할 건지 정의
doThrow
스터빙 메소드 호출 후 어떤 Exception을 throw할  건지 정의
doAnswer
스터빙 메소드 호출 후 작업을 할지 custom하게 정의
doNothing
스터빙 메소드 호출 후 어떤 행동도 하지 않게 정의
doCallRealMethod
실제 메소드 호출

@Mock

NOTE
@Mock은 mock객체는 가짜 객체이며, 메소드 호출해서 사용하기 위해선 Stubbing을 해야한다!
@Test public void NotUseMockAnnotation() { // Mock 생성 List mockList = Mockito.mock(ArrayList.class); // Mock 사용방법 mockList.add("one"); // 데이터 추가(실제로는 추가되지 않음) Mockito.verify(mockList).add("one"); // 호출 검사 assertEquals(0, mockList.size()); // 성공! Mockito.when(mockList.size()).thenReturn(100); // 해당함수 실행시 100반환 assertEquals(100, mockList.size()); // 성공! }
Java
복사
Mockito.mock() 방식으로 생성
Mockito로 생성된 mock객체는 실제 객체의 동작을 수행하지 않고, 테스트에서 정의된 동작만을 수행한다. (add를 해도 size는 증가하지 않는다.)
@Mock List<String> mockedList; @Test public void whenUseMockAnnotation_thenMockIsInjected() { mockedList.add("one"); Mockito.verify(mockedList).add("one"); assertEquals(0, mockedList.size()); Mockito.when(mockedList.size()).thenReturn(100); assertEquals(100, mockedList.size()); }
Java
복사
어노테이션으로 생성

@Spy

NOTE
@Spy로 만든 Mock객체는 진짜 객체이며, 메소드 실행시 Stubbing을 하지 않으면 기존 객체의 로직을 실행한 값을 리턴한다!
@Test public void whenNotUseSpyAnnotation_thenCorrect() { List<String> spyList = Mockito.spy(new ArrayList<String>()); spyList.add("one"); spyList.add("two"); // 호출 검사 Mockito.verify(spyList).add("one"); Mockito.verify(spyList).add("two"); // 실제로 값이 추가됨! assertEquals(2, spyList.size()); // 100을 반환하도록 설정 Mockito.doReturn(100).when(spyList).size(); assertEquals(100, spyList.size()); }
Java
복사
Mockito.spy() 방식으로 생성
@Spy List<String> spiedList = new ArrayList<String>(); @Test public void whenUseSpyAnnotation_thenSpyIsInjectedCorrectly() { spiedList.add("one"); spiedList.add("two"); Mockito.verify(spiedList).add("one"); Mockito.verify(spiedList).add("two"); assertEquals(2, spiedList.size()); Mockito.doReturn(100).when(spiedList).size(); assertEquals(100, spiedList.size()); }
Java
복사
어노테이션으로 생성

@InjectMocks

NOTE
@InjectMocks는 DI를 @Mock이나 @Spy로 생성된 mock객체를 자동으로 주입해주는 어노테이션이다!
public class OrderService { UserService userService; ProductService productService; OrderService(UserService userService, ProductService productService) { this.userService = userService; this.productService = productService; } public User getUser() { return userService.getUser(); } public Product getProduct() { return productService.getProduct(); } }
Java
복사
실제코드) OrderService에 DI를 해주는 코드
@ExtendWith(MockitoExtension.class) public class InjectMocksAnnotation { @Mock UserService userService; @Spy ProductService productService; @InjectMocks // OrderService의 DI인 UserService와 ProductService가 자동으로 추가됨 OrderService orderService; @Test void testGetUser() { assertNull(orderService.getUser()); } @Test void testGetProduct() { Product product = orderService.getProduct(); assertEquals("A001", product.getSerial()); } }
Java
복사
@Mock 또는 @Spy로 생성된 가짜 객체를 자동으로 주입시켜주는 어노테이션

verify 메소드

NOTE
verify메소드를 이용해서 스터빙한 메소드가 실행되었는지, n번 실행되었는지, 실행이 초과되지 않았는지 검증이 가능하다!
// 메서드 형식 verify(T mock, VerificationMode mode) @ExtendWith(MockitoExtension.class) public class VerifyMethod { @Mock UserService userService; @Test void 호출_2번() { userService.getUser(); userService.getUser(); verify(userService, times(2)).getUser(); } @Test void 호출되지않음() { verify(userService, never()).getUser(); } @Test void 최소호출_1번() { userService.getUser(); verify(userService, atLeastOnce()).getUser(); } @Test void 최소호출_2번() { userService.getUser(); userService.getUser(); userService.getUser(); verify(userService, atLeast(2)).getUser(); } @Test void 최대호출_1번() { userService.getUser(); // userService.getUser(); - 2번 이상 실행하면 fail verify(userService, atMostOnce()).getUser(); } @Test void 최대호출_3번() { userService.getUser(); userService.getUser(); userService.getUser(); // userService.getUser(); - 6번 이상 실행하면 fail verify(userService, atMost(3)).getUser(); } @Test void 호출_지정함수() { userService.getUser(); // userService.getLoginErrNum(); - getUser()가 아닌 다른 메소드 실행 시 fail verify(userService, only()).getUser(); } }
Java
복사
메소드명
설명 (테스트 내에서~)
times(n)
몇 번이 호출됐는지 검증
never
한 번도 호출되지 않았는지 검증
atLeastOne
최소 한 번은 호출됐는지 검증
atLeast(n)
최소 n 번이 호출됐는지 검증
atMostOnce
최대 한 번이 호출됐는지 검증
atMost(n)
최대 n 번이 호출됐는지 검증
calls(n)
n번이 호출됐는지 검증 (InOrder랑 같이 사용해야 함)
only
해당 검증 메소드만 실행됐는지 검증
timeout(long mills)
n ms 이상 걸리면 Fail 그리고 바로 검증 종료
after(long mills)
n ms 이상 걸리는지 확인timeout과 다르게 시간이 지나도 바로 검증 종료가 되지 않는다.
description
실패한 경우 나올 문구

Argument matchers

NOTE
// any => 어떠한 값이든 타입에 맞으면 상관없다. when(mockedList.get(anyInt())).thenReturn("int"); when(mockedList.add(anyFloat())).thenReturn(true); when(mockedList.add(anyString())).thenReturn(true); System.out.println(mockedList.get(999)); // int System.out.println(mockedList.add(3.3)); // true System.out.println(mockedList.add("string")); // true // eq(Class) => 해당 클래스와 동일한지 확
Java
복사