ํ ์คํธ ๋ชจํน Mockito ๋ผ์ด๋ธ๋ฌ๋ฆฌ
by JiwonDev2021.09.07 - [Backend/Java] - ๋ค์ํ ํ ์คํธ์ Test Double (+Mockist)
๋ค์ํ ํ ์คํธ์ Test Double (+Mockist)
Mockito ๋ ํผ๋ฐ์ค https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html Mockito - mockito-core 3.12.4 javadoc Latest version of org.mockito:mockito-core https://javadoc.io/d..
jiwondev.tistory.com
# Mockito๋?
https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html
ํ ์คํธ์ ํ์ํ Mock Object๋ฅผ ์์ฑ, ๊ฒ์ฆ, ์คํฐ๋น(Creation, Verification, Stubbing)ํด์ฃผ๋ ์๋ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค.
gradle์์ ๋ค์๊ณผ ๊ฐ์ด ์์กด์ฑ์ ์ถ๊ฐํด ์ฌ์ฉํ ์ ์๋ค.
โก ๋ณดํต Junit, AssertJ, Mockito๋ฅผ ํจ๊ป ์ฌ์ฉํ๋ค.
dependencies { testImplementation('org.junit.jupiter:junit-jupiter:5.6.0') testImplementation('org.assertj:assertj-core:3.11.1') testImplementation('org.mockito:mockito-core:3.3.0') }
์คํ๋ง์ ์ฌ์ฉํ๋ค๋ฉด, ์คํ๋ง ๋ถํธ ํ ์คํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ Mockito๊ฐ ์ด๋ฏธ ํฌํจ๋์ด์์ด ๋ฐ๋ก ์ฌ์ฉํ ์ ์๋ค.
์ฐธ๊ณ ๋ก ์คํ๋งํ ์คํธ์ Junit, assertj, hamcrest, xmlunit ๋ฑ ๋ง์ด ์ฌ์ฉํ๋ ํ ์คํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ํฌํจ๋์ด์๋ค.
testImplementation 'org.springframework.boot:spring-boot-starter-test'

# 1. ๊ธฐ๋ณธ ๋์ (์์ฑ-๊ฒ์ฆ-์คํฐ๋น)
์๋ฐ์ ๊ฐ์ฒด๋ฅผ mock(...) ๋ฉ์๋๋ก Stubingํ ์ ์๋ค. ์ด๋ ๊ฒ Stubing ํ ๊ฐ์ฒด๋ฅผ ์ด์ฉํด ๋ค์ํ Mocking์ ๋ง๋ ๋ค.
import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; class EX1_Mockito { @Test void stubbing() throws Exception{ // 1. Mock ๊ฐ์ฒด๋ฅผ ์์ฑํจ. List<String> mockedList = mock(ArrayList.class); // 2. when์ ์ด์ฉํ stubbing. ๊ฐ์ง ๋์์ ์ง์ ํจ. when(mockedList.get(0)).thenReturn("First"); when(mockedList.get(1)).thenThrow(new IndexOutOfBoundsException()); // 3-1 Junit์ด๋ AssertJ๋ฅผ ์ด์ฉํ์ฌ ๋ด๊ฐ ์ํ๋ ๋์์ ํ๋์ง ํ์ธํจ. assertThat(mockedList.get(0)).isEqualTo("First"); // ๋ฐํ๊ฐ์ด First์ธ๊ฐ? assertThatThrownBy(() -> mockedList.get(1)) // ํด๋น ๋ฉ์๋๋ .isInstanceOf(IndexOutOfBoundsException.class); // ์ด ์์ธ๋ฅผ ๋ฐํํ๋๊ฐ? assertThat(mockedList.get(10)).isNull(); // ํด๋น ๋ฉ์๋์ ๊ฒฐ๊ณผ๊ฐ Null์ธ๊ฐ? // 3-2 ๋ฐํ๊ฐ์ด ์๋ ๊ฒฝ์ฐ. ํด๋น ๋ฉ์๋๊ฐ ํธ์ถ๋์๋์ง ์ฌ๋ถ๋ ํ์ธ๊ฐ๋ฅํ๋ค. // ํ๋ผ๋ฏธํฐ๋ ๊ฒ์ฆํ๋ค. verify(mockedList).get(2); ๋ง ํธ์ถ๋์๋ค๋ฉด ์คํจ! verify(mockedList, times(1)).get(0); verify(mockedList).get(0); // ์๋ต์ 1๋ฒ } }
์ฐธ๊ณ ๋ก verify๋ BDD Mockito๋ก ์๋์ ๊ฐ์ด ๋ณ๊ฒฝํ ์ ์๋ค.
๋ฉ์๋ ์ด๋ฆ๋ง ๋ค๋ฅผ ๋ฟ, ๋์์ ๋ด๋ถ ๋์์ ๋น์ทํ๋ค.
@Test void startBDDStyle(){ //Given Study study = new Study("test", 10); Member member = new Member(); member.setEmail("delusidiot@gmail.com"); member.setId(1L); BDDMockito.given(memberServiceMock.findById(1L)).willReturn(Optional.of(member)); BDDMockito.given(repositoryMock.save(study)).willReturn(study); StudyService studyService = new StudyService(memberServiceMock, repositoryMock); //When studyService.createNewStudy(1L, study); //Then BDDMockito.then(memberServiceMock).should(times(1)).notify(study); BDDMockito.then(memberServiceMock).should().notify(member); // ์๋ต์ 1๋ฒ BDDMockito.then(memberServiceMock).shouldHaveNoInteractions(); // ์ฐธ๊ณ ๋ก ํน์ ์์ ์ดํ๋ก ์ธํฐ๋ ์
์ด ์๋์ง ํ์ธํ๊ณ ์ถ๋ค๋ฉด ..NoMoreInteraction()๋ฅผ ์ฐ์ }
# 2. Argument Matcher
Mockito๋ ๋ค์ํ ArgumentMatcher๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ๊ฐ ๋ฟ๋ง ์๋๋ผ ๋ ผ๋ฆฌ์ ์ธ ํ์ ์ผ๋ก ์คํฐ๋น ๊ฐ๋ฅํ๋ค.
์๋ฅผ ๋ค์ด AnyInt(), AnyString()์ ํ๋ผ๋ฏธํฐ์ ์ ๋ฌํด์ ํด๋น ํ์ ์ ์ฃผ์์ ๋ ์์ธ๊ฐ ๋ฐ์ํ๋์ง ๊ฒ์ฆ๊ฐ๋ฅํ๋ค.
@Test void argument_matchers() throws Exception{ List<String> mockedList = mock(ArrayList.class); when(mockedList.get(anyInt())).thenReturn("AnyInt"); assertThat(mockedList.get(1010)).isEqualTo("AnyInt"); verify(mockedList).get(anyInt()); // verify๋ ์ฌ์ฉ๊ฐ๋ฅ! }
๋ฌผ๋ก ํ์ํ๋ค๋ฉด ๋ ผ๋ฆฌ์ ์ธ ํ๋ผ๋ฏธํฐ๋ฅผ ArgumentMatcher๋ฅผ ์ง์ ๊ตฌํํด์ ๋ง๋ค ์ ์์ต๋๋ค.
- ํ๋ผ๋ฏธํฐ๊ฐ ๋ค์ด๊ฐ ์๋ฆฌ์ argThat ๊ฐ์ฒด๋ฅผ ์์ฑํด์ ์ฃผ๋ฉด ๋ฉ๋๋ค.
@Test void matcherSupport() throws Exception { final List<String> mockedList = mock(List.class); mockedList.add("Hello"); mockedList.add("Dexter"); mockedList.add("James"); // when์์ anyInt() ๋์ ์ any[ํฌ๊ธฐ๊ฐ 3 ์ดํ์ธ ๋ฆฌ์คํธ]๋ฅผ ๋ฃ๊ณ ์ถ์ ๊ฒฝ์ฐ when(mock.someMethod(argThat(list -> list.size()<3))).thenReturn(null); // verify๋ ๋๊ฐ์ด ์ฌ์ฉํ๋ฉด ๋๋ค. ๊ทธ๋ฅ ๋ฉ์๋ ํ๋ผ๋ฏธํฐ์๋ฆฌ์ argThat์ ๋ง๋ค๋ฉด ๋จ. verify(mockedList, times(3)) .add( // add(..) ๋ฉ์๋์ ๋ค์ด๊ฐ ํ๋ผ๋ฏธํฐ ๊ฐ์ ๊ธธ์ด๊ฐ 3์ด๊ณผ, 8๋ฏธ๋ง์์ ๊ฒ์ฆํจ argThat(string -> (string.length() < 8) && (string.length() > 3)) ); }
๋ฌผ๋ก Java8์ ๋๋ค์ ๋์ ์ ์ต๋ช ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ์ ๋ ๋ฉ๋๋ค.
// JAVA 8 ๋๋ค๋ฅผ ์ด์ฉํ๋ ๋ฐฉ๋ฒ verify(list, times(2)).add(argThat(string -> string.length() < 5)); // JAVA 7 ์ต๋ช
ํด๋์ค๋ฅผ ์ด์ฉํ๋ ๋ฐฉ๋ฒ verify(list, times(2)).add(argThat(new ArgumentMatcher(){ public boolean matches(String arg) { return arg.length() < 5; } })); // ๊ฐ์ฒด๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ๋ ๊ฒฝ์ฐ, receiveComplexObject๋ก ํ๋ผ๋ฏธํฐ ๊ฐ์ฒด ๊ฒ์ฆ๊ฐ๋ฅ. verify(target, times(1)).receiveComplexObject( argThat( obj -> obj.getSubObject().get(0).equals("expected") ) );
# 3. verify ์ถ๊ฐ ๊ธฐ๋ฅ
verify๋ ๋ณดํต ํด๋น ๊ธฐ๋ฅ์ด ์ฌ์ฉ๋์๋์ง ์ฌ๋ถ๋ฅผ ๊ฒ์ฆํ๋ค.
ํ์ง๋ง ์ถ๊ฐ์ ์ธ ํ๋ผ๋ฏธํฐ๋ฅผ ์ฃผ๋ฉด ํด๋น ๊ธฐ๋ฅ์ด ๋ช๋ฒ ์ฌ์ฉ๋์๋์ง, ํ๋ฒ๋ ์ฌ์ฉ๋์ง ์์๋์ง๋ ๊ฒ์ฆ ๊ฐ๋ฅํ๋ค.
@Test void verify_mockito() throws Exception{ List<String> mockedList = mock(ArrayList.class); mockedList.add("once"); mockedList.add("twice"); mockedList.add("twice"); mockedList.add("three times"); mockedList.add("three times"); mockedList.add("three times"); // 1. add("once")๊ฐ ๋ช๋ฒ ํธ์ถ๋์๋์ง ํ์ธํ ์ ์๋ค. verify(mockedList).add("once"); // time(1)์ ๊ธฐ๋ณธ๊ฐ์ด๋ผ ์๋ตํ ์ ์๋ค. verify(mockedList, time(1)).add("once"); verify(mockedList, times(2)).add("twice"); verify(mockedList, times(3)).add("three times"); // 2. ๊ทธ ์ธ ๋ค์ํ ์ถ๊ฐ ํ๋ผ๋ฏธํฐ๋ฅผ ์ ๊ณตํ๋ค. never()๋ ํ๋ฒ๋ ํธ์ถ๋์ง ์์๊ฒฝ์ฐ OK verify(mockedList, atLeastOnce()).add("once"); verify(mockedList, atMostOnce()).add("once"); verify(mockedList, atLeast(1)).add("twice"); verify(mockedList, atMost(10)).add("three times"); verify(mockedList, never()).add("mockito"); }
## any, ํน์ ํ์ ์ด ์ฌ์ฉ๋์๋์ง ํ์ธ(์์, ์์๋ฑ)
์ฐธ๊ณ ๋ก ์ ๋ ฅ๊ฐ์ด ์๋๋ผ ํน์ ํ์ ์ธ์ง ๊ฒ์ฌํ๊ณ ์ถ์ ๊ฒฝ์ฐ any๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค.
์์์ ์ค๋ช ํ Argument Matcher๋ฅผ ํตํด ํ์ ์ ๊ฒ์ฌํ๋ค. ๋จ ๊ธฐ๋ณธํ์ ํ์ ๋ณํ์ด ๋ถ๊ฐ๋ฅํด์, anyInt() ๊ฐ์ ๊ฑธ ์ฌ์ฉํด Integer๋ก ์ ์บ์คํ ํด์ ํ์ ์ ๊ฒ์ฌํ๋ค.
verify(mockedList).add(any()); // ์
๋ ฅ๊ฐ์ด Object๋ก ๋ณํ ๊ฐ๋ฅํ๊ฐ? (primitive ํ์
๋ฏธํฌํจ) verify(mockedList).add(any(MyClass.class)); // ์
๋ ฅ๊ฐ์ด MyClass.class๋ก ๋ณํ๊ฐ๋ฅํ๊ฐ? verify(bar).doPrimitiveStuff(anyInt()); // ์
๋ ฅ๊ฐ์ด Integer๋ก ๋ณํ ๊ฐ๋ฅํ๊ฐ?
@MockBean private JavaMailSender mockMailSender; // mockMailSender.send( ..ํด๋น ํ์
.. ) ๋ฉ์๋๊ฐ 1๋ฒ ์คํ๋์๋์ง๋ฅผ ํ์ธํ๋ค. verify(mockMailSender).send( any(SimpleMailMessage.class) ); then(mockMailSender).should().send( any(SimpleMailMessage.class) );

# 4. void method ์์ธ ์คํฐ๋น
๋ฐํ๊ฐ์ด void์ธ ๊ฒฝ์ฐ Mockito.when( ).method... ์ฒ๋ผ ์ฌ์ฉํ ์ ์์ด ๋ต๋ตํ๋ค. โก ์ด ๋๋ doThorw, doNothing ์ฌ์ฉ
@Test void doThrow_mockito() throws Exception { List<String> mockedList = mock(ArrayList.class); // void clear(); ๊ฐ ํธ์ถ๋ ๋ ์์ธ๋ฅผ ๋์ง๋๋ก Stubingํ๋ค. doThrow(new RuntimeException("Boom!")).when(mockedList).clear(); // clear ๋ฉ์๋๊ฐ ์์ธ๋ฅผ ๋ฐํํ๋์ง ๊ฒ์ฆ assertThatThrownBy(() -> mockedList.clear()) .isInstanceOf(RuntimeException.class) .hasMessage("Boom!"); // ์๋๋ฉด doNothing()์ ์ฌ์ฉํด๋ ๋๋ค. Person p = mock(Person.class); doNothing().when(p).setName("mike"); p.setName("mike"); verify(p).setName("mike"); }
# 5. ํธ์ถ๋ ์์ ๊ฒ์ฆํ๊ธฐ - inOrder
@Test void verify_in_order() throws Exception { List<String> firstMockedList = mock(ArrayList.class); List<String> secondMockedList = mock(ArrayList.class); firstMockedList.add("firstMockList add first"); firstMockedList.add("firstMockList add second"); secondMockedList.add("secondMockList add first"); secondMockedList.add("secondMockList add second"); // 1. inOrder๋ก Mock ๊ฐ์ฒด๋ฅผ ๋ฌถ์ด์ ๋ฉ์๋์ ํธ์ถ ์์๋ ๊ฒ์ฆํ ์ ์๋ค. final InOrder inOrder = inOrder(firstMockedList, secondMockedList); // 2. ์ฌ์ฉ์ inOrder.verify ๋ฑ์ ์ด์ฉํ๋ฉด ๋๋ค. inOrder.verify(firstMockedList).add("firstMockList add first"); // 1๋ฒ์จฐ inOrder.verify(firstMockedList).add("firstMockList add second"); // 2๋ฒ์จฐ inOrder.verify(secondMockedList).add("secondMockList add first"); // 3๋ฒ์จฐ inOrder.verify(secondMockedList).add("secondMockList add second"); // 4๋ฒ์งธ }
# 6. @Mock Annotaion
Mockito.mock() ์ ์ด์ฉํ์ง ์๊ณ , ์ด๋ ธํ ์ด์ ์ผ๋ก ํธํ๊ฒ Mock ๊ฐ์ฒด๋ฅผ ๋ง๋ค ์ ์๋ค.
๋ค๋ง Mockito์ ํน์ฑ์, ํ๋ผ๋ฏธํฐ๊ฐ ์๋ ๊ธฐ๋ณธ์์ฑ์๊ฐ ์กด์ฌํด์ผ Mock ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด์ ์ฌ์ฉํ ์ ์๋ค.
- ์ฐธ๊ณ ๋ก ์ด๋
ธํ
์ด์
์ ์ฌ์ฉํ๋ ค๋ฉด MockitoAnnotation.openMocks(ํ์ฌ ํ
์คํธ ํด๋์ค)๋ฅผ ๋ฐ๋์ ํด์ฃผ์ด์ผํ๋ค.
โก JUnit5 - MockitoJUnitRunner์ ํด๋น ๋ฉ์๋๊ฐ ํฌํจ๋์ด์์ด ์ ๊ฒฝ์ฐ์ง ์์๋ ์๊ด ์๊ธด ํ๋ค.
// Product.java public class Product { private Long id; private String name; private int price; public Product() { // ์ด๊ฑฐ ํ์. } public Product(Long id, String name, int price) { this.id = id; this.name = name; this.price = price; } // getters, setters... }
@Mock private Product product; // ์์์ Mock์ผ๋ก ์์ฑ. @Test void mockAnnotation() throws Exception{ // 1. product ์ ์ธ๋งํ๊ณ ๋ฐ๋ก ์ฌ์ฉํ๋ฉด ๋จ. MockitoAnnotations.initMocks(this); when(product.getId()).thenReturn(3L); assertThat(product.getId()).isEqualTo(3L); verify(product).getId(); }
# 7. ์ฐ์์ผ๋ก ์ฌ๋ฌ๊ฐ ์คํฐ๋นํ๊ธฐ
Mock๊ฐ์ฒด์ ๊ฐ์ง๊ฐ์ ๋ฑ๋กํ ๋, ํ๋ฒ์ ์ฌ๋ฌ๊ฐ๋ฅผ ์ฒด์ด๋ํด์ ๋ฑ๋กํ ์ ์๋ค.
@Test void consecutive_call() throws Exception{ // 1. ํจ์ ์ฒด์ด๋ List<String> mockedList = mock(List.class); when(mockedList.get(0)) // when์ ํด๋น ๋ฉ์๋๊ฐ ๋์์ ๋ง์ณค์ ๋ ๋ฐํ๊ฐ์ ์ง์ ํ๋ค. .thenReturn("Hello") // ๋ฐํ๊ฐ "Hello" ์ง์ .thenReturn("Dexter") // ์ค๋ณตํ๋ฉด ๋ฎ์ด์์ฐ๋๊ฒ ์๋๋ผ, ๋ค์ ํธ์ถ์ ์ฌ์ฉ๋๋ค. .thenThrow(new RuntimeException("Boom!")); // ๋ฐ์ํ ์์ธ ์ง์ ์ง์ assertThat(mockedList.get(0)).isEqualTo("Hello"); assertThat(mockedList.get(0)).isEqualTo("Dexter"); assertThatThrownBy(() -> mockedList.get(0)) .isInstanceOf(RuntimeException.class) .hasMessage("Boom!"); // 2. ํ๋ผ๋ฏธํฐ ์ ๋ฌ List<String> mockedList2 = mock(List.class); when(mockedList2.get(0)) .thenReturn("Hello", "Dexter"); // ์๋๋ฉด ์ด๋ ๊ฒ ํ๋ผ๋ฏธํฐ๋ก ํ๊บผ๋ฒ์ ์ค๋ ๋๋ค. assertThat(mockedList2.get(0)).isEqualTo("Hello"); assertThat(mockedList2.get(0)).isEqualTo("Dexter"); // 3. ์์ when์ ์ฌ๋ฌ๋ฒ ํธ์ถ -> ์ด๋ ๋ฎ์ด์์์ง๋ค. List<String> mockedList3 = mock(List.class); when(mockedList3.get(0)) .thenReturn("Hello"); when(mockedList3.get(0)) .thenReturn("Dexter"); // ๊ธฐ์กด์ when("Hello")๊ฐ ์ญ์ ๋๋ค. assertThat(mockedList2.get(0)).isEqualTo("Dexter"); assertThat(mockedList2.get(0)).isEqualTo("Dexter"); }
# 8. Custom ์คํฐ๋น ๋ง๋ค๊ธฐ
์ด๋ฏธ ์กด์ฌํ๋ thenRetun, thenThrow๋ง ์ฌ์ฉํด๋ ๋์ง๋ง, ํ์ํ๋ค๋ฉด ์๋์ ๊ฐ์ด ์ง์ ๋ง๋ค์๋์๋ค.
โก thenAnswer์ ์ด์ฉํด, ์ต๋ช ํด๋์ค ์ ์ or Lambda์์ ์ด์ฉํด์ ๊ตฌํํ๋ฉด ํธํ๋ค.
โก Java์์๋ ๊ฐ์ฒด์ ์ถ์๋ฉ์๋๊ฐ ๋จ 1๊ฐ์ธ ๊ฒฝ์ฐ, ์๋์ ๊ฐ์ด new ๋ฅผ ์๋ตํ๊ณ ๋ฐ๋ก ํจ์์ฒ๋ผ ๊ฐ์ฒด๋ฅผ ๋ง๋ค ์ ์๋ค.
// doSomething ๋ฉ์๋๊ฐ ํธ์ถ๋๋ฉด 12๋ฅผ ๋ฐํํ๋๋ก ์คํฐ๋น doAnswer(invocation -> 12).when(mock).doSomething(); // domSomething(str,str,str) ๋ฉ์๋๊ฐ ํธ์ถ๋๋ฉด ์ฒซ๋ฒ์งธ ํ๋ผ๋ฏธํฐ์ ๊ธธ์ด๋ฅผ ๋ฐํํ๋๋ก ์คํฐ๋น doAnswer( invocation -> ((String)invocation.getArgument(1)).length() ) .when(mock) .doSomething(anyString(), anyString(), anyString());
@Test void answer_callback() throws Exception { List<String> mockedList = mock(List.class); when(mockedList.get(123)) .thenAnswer( invocation -> { // ํด๋น ๋ฉ์๋๋ก ๋์์ด ๋์ฒด๋๋ค. final Object[] arguments = invocation.getArguments(); return "์ ๋ฌ๋ ์๊ท๋จผํธ๋ค: " + Arrays.toString(arguments); } ); assertThat(mockedList.get(123)).isEqualTo("์ ๋ฌ๋ ์๊ท๋จผํธ๋ค: [123]"); }
# 9. Spy Mock ๊ฐ์ฒด
์ค์ ๊ฐ์ฒด์ ์ธ์คํด์ค๋ฅผ ๋ณต์ฌํ์ฌ ๋๊ฐ์ด ๋์ํ๋ Mock์ ๋ง๋ค ์ ์๋ค.
=> ์ธ์คํด์ค๊ฐ ๊ฐ์ง๊ณ ์๋ ๊ฐ๊น์ง ๋๊ฐ์ด ๋ณต์ฌํ๋ค.
@Test void spy_mock() throws Exception{ final List<String> nameList = new ArrayList<>(); final List<String> spy = spy(nameList); // spy ๊ฐ์ฒด ์์ฑ // 1. ๋น์ด์๋ ๋ฆฌ์คํธ๋ฅผ ๋ณต์ฌํ์ผ๋ฏ๋ก ๋ถ๊ฐ๋ฅํ ์คํฐ๋น์ด๋ค. (IndexOutOfBoundsException ๋ฐ์) // when(spy.get(0)).thenReturn("Dexter"); // 1.1 ๋ฌผ๋ก ์ด๋ฐ๊ฑด ๊ฐ๋ฅํ๋ค. when(spy.size()).thenReturn(100); // 2. spy ๊ฐ์ฒด๋ ์ด๋ ๊ฒ doReturn์ ์ด์ฉํ์ฌ ์คํฐ๋นํ๋ฉด ๋๋ค. doReturn("Dexter").when(spy).get(0); assertThat(spy.get(0)).isEqualTo("Dexter"); // 3. ์ค์ ๊ฐ์ฒด์ฒ๋ผ ๋์ํ์ง๋ง, ๋น์ฐํ ์ค์ ๊ฐ์ฒด์ ์ ์ฉ๋ ๊ฒ์ด ์๋๋ค. // ๊ทธ๋์ nameList.get()์ ํธ์ถํด๋ ์คํฐ๋น๋ ๊ฐ์ด ๋์ค์ง์์. assertThatThrownBy(() -> nameList.get(0)) .isInstanceOf(IndexOutOfBoundsException.class); }
# 10. Mock ๊ฐ์ฒด ์์ฑ ์ ๋ต์ค์ (๊ฑฐ์ ์ฌ์ฉํ์ง ์์)
mock ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ณ ๋ฐ๋ก ์คํฐ๋น์ ํด์ฃผ์ง ์์ผ๋ฉด, ๊ธฐ๋ณธํ์ (int,boolean)์ ์ ์ธํ๊ณ ๋ ์ ๋ถ null์ ๋ฐํํ๋๋ก ๋ง๋ค์ด์ง๋ค. โก mock ๊ฐ์ฒด์ ์์ฑ์ ๋ต์ ์ง์ ํด์ฃผ์ด์ ์๋์ผ๋ก ๋ฐํ๊ฐ์ ๋ง๋ค๋๋ก ์ง์ ํ ์ ์๋ค.
* ๋ค๋ง ์ด๋ ์ค๋๋ ๋ ๊ฑฐ์ ์์คํ ์ ํ ์คํธํ๋๊ฒ ์๋๋ผ๋ฉด, ์ด๋ฐ์์ผ๋ก ์ฌ์ฉํ ์ผ์ด ์๋ค. ๊ทธ๋ฅ ์์๋ง ๋์
@Test void default_returnMock() throws Exception { // 1. ๊ทธ๋ฅ mock ๊ฐ์ฒด๋ฅผ ๋ง๋ค๋ฉด, ๊ธฐ๋ณธ ๋ฐํ๊ฐ์ ์ ๋ถ null์ด๋ค. final Product mock = mock(Product.class); assertThat(mock.getName()).isNull(); assertThatThrownBy(() -> mock.getName().length()) .isInstanceOf(NullPointerException.class); // 2. RETURNS_SMART_NULL์ ์์ฑ์ ๋ต์ผ๋ก ์ง์ ํ ๊ฒฝ์ฐ. // NPE ์๋ฌ๊ฐ ๋ฐ์ํ์ง ์๋๋ก ๋ง๋ค ์ ์๋ค. final Product mockWithSmartNulls = mock(Product.class, RETURNS_SMART_NULLS); assertThat(mockWithSmartNulls.getName()).isNotNull(); assertThat(mockWithSmartNulls.getName().length()).isEqualTo(0); }
# 11. ArgumentCaptor
verify(Object).method(...) ์ฌ์ฉ ์ ํ๋ผ๋ฏธํฐ๋ฅผ ์ง์ ์ง์ ํ์ง๋ง, ArgumentCaptor๋ฅผ ์ด์ฉํ๋ฉด ์ ์ฐํ๊ฒ ์ฌ์ฉํ ์ ์๋ค.
@Test void capture() throws Exception{ // stubbing final List<String> mockedList = mock(List.class); when(mockedList.get(1)).thenReturn("A"); when(mockedList.get(2)).thenReturn("B"); when(mockedList.get(3)).thenReturn("C"); // 1. Integer ๊ฐ์ ๋ค๋ฃจ๋ ArgumentCaptor ์์ฑ ArgumentCaptor<Integer> integerArgumentCaptor = ArgumentCaptor.forClass(Integer.class); assertThat(mockedList.get(1)).isEqualTo("A"); assertThat(mockedList.get(3)).isEqualTo("C"); assertThat(mockedList.get(2)).isEqualTo("B"); // 2. ์
๋ ฅ๋ ํ๋ผ๋ฏธํฐ๋ฅผ ๋ค์ Captor(ํ๋)ํด์ ์ฌ์ฌ์ฉํ ์ ์๋ค. final List<Integer> allValues = integerArgumentCaptor.getAllValues(); assertThat(allValues).isEqualTo(Arrays.asList(1, 3, 2)); // 3. ArgumentCaptor๋ฅผ ์ด์ฉํ๋ฉด times(3) ์ฒ๋ผ ๋์ ์ผ๋ก ํ๋ผ๋ฏธํฐ๋ฅผ ์์ฑํ ์ ์๋ค. verify(mockedList, times(3)).get(integerArgumentCaptor.capture()); // verify(mockedList).get(1); // verify(mockedList).get(2); // verify(mockedList).get(3); }
# 12. Resetting Mocks (๊ฑฐ์ ์ฌ์ฉํ์ง ์์)
์คํฐ๋น ๋ ๊ฐ์ mock ๊ฐ์ฒด์ ์ด๊ธฐ์ํ(null)๋ก ๋๋๋ ค๋ฒ๋ฆด ์ ์๋ค. ๋ค๋ง ํ ์คํธ๋ฅผ ์ด๋ฐ๊ตฌ๋ก ํ๋๊ฑด ์ข์ง์์ ๋ฐฉ๋ฒ์ด๋ค.
@Test void reset_mock() throws Exception{ final Product mock = mock(Product.class); when(mock.getName()).thenReturn("Dexter"); assertThat(mock.getName()).isEqualTo("Dexter"); reset(mock); assertThat(mock.getName()).isNull(); }
# 13. Behavior Driven Development (BDD)
ํ์ ์ฃผ๋ ์ค๊ณ BDD๋ผ๊ณ ๋ถ๋ฆฌ๋ฉฐ given->when->then ๊ตฌ์กฐ๋ฅผ ์๋ฏธํ๋ค.
Mocktio์ ๊ธฐ๋ณธ ๋ฉ์๋๋ค, when().thenReturn()๋ฑ์ ์ด๊ฒ ์คํฐ๋น์ ํ๋๊ฑด์ง, ๊ฒ์ฆ์ ํ๋๊ฑด์ง ์๋ฏธ๊ฐ ๋ช ํํ์ง ์๋ค.
โก ๋๋ ์ฒ์ ๊ณต๋ถํ ๋๋ when()์ด ์คํฐ๋น์ด ์๋๋ผ ๊ฒ์ฆํ๋ ์ฝ๋๋ก ์ฐฉ๊ฐํ์๋ค. ๋๋ง ๊ทธ๋ฐ๊ฒ ์๋์๊ตฌ๋ ใ ใ ;
๊ทธ๋์ mockito.BDDMockito๋ผ๋ ์ถ๊ฐ ํจํค์ง๋ฅผ ์ฌ์ฉํ๋ฉด BDD๋ฅผ ๊ตฌ๋ถํ๊ธฐ ์ข์ ํ์คํ ๋ฉ์๋๋ค์ ์ฌ์ฉํ ์ ์๋ค.
- given(product.getName()).willReturn("Dexter"); => when๊ณผ ๊ฐ์ ๊ธฐ๋ฅ
- then(product).should(times(1)).getName(); => verify์ ๊ฐ์ ๊ธฐ๋ฅ
import org.junit.jupiter.api.*; import org.mockito.*; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.*; // ์ถ๊ฐ ํจํค์ง ์ฌ์ฉ public class EX2_Mockito_BDD { @Mock Product product; @BeforeEach void init(){ MockitoAnnotations.initMocks(this); } @Test void BDD_test() throws Exception{ //given(...).willReturn()์ผ๋ก ์ข ๋ ๋ช
ํํ ์ด๋ฆ์ ์ฌ์ฉํ๋ค. given(product.getName()).willReturn("Dexter"); //when final String name = product.getName(); //then assertThat(name).isEqualTo("Dexter"); then(product).should(times(1)).getName(); // verify๋ ๊ฐ์ ๊ธฐ๋ฅ! } }
# 14. @Captor, @Spy
์์์ @Mock ์ฒ๋ผ ์ด๋ ธํ ์ด์ ์ ์ด์ฉํด์ ArugmentCaptor, Spy๋ฅผ ๋ง๋ค ์ ์๋ค.
@Captor ArgumentCaptor<Integer> integerArgumentCaptor; @Test void capture() throws Exception { MockitoAnnotations.initMocks(this); final List<String> mockedList = mock(List.class); when(mockedList.get(1)).thenReturn("A"); when(mockedList.get(2)).thenReturn("B"); when(mockedList.get(3)).thenReturn("C"); assertThat(mockedList.get(1)).isEqualTo("A"); assertThat(mockedList.get(3)).isEqualTo("C"); assertThat(mockedList.get(2)).isEqualTo("B"); verify(mockedList, times(3)).get(integerArgumentCaptor.capture()); final List<Integer> allValues = integerArgumentCaptor.getAllValues(); assertThat(allValues).isEqualTo(Arrays.asList(1, 3, 2)); }
@Spy Product productSpy = new Product(1L, "Dexter", 1000); @Spy Product productSpyWithNoArgCon; // new Product(); @Test void spy_annotation() throws Exception { MockitoAnnotations.initMocks(this); assertThat(productSpy.getName()).isEqualTo("Dexter"); assertThat(productSpyWithNoArgCon.getName()).isNull(); when(productSpy.getName()).thenReturn("James"); when(productSpyWithNoArgCon.getName()).thenReturn("James"); assertThat(productSpy.getName()).isEqualTo("James"); assertThat(productSpyWithNoArgCon.getName()).isEqualTo("James"); }
# 14-1 @InjectMocks
@InjectMocks๋ก ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ฉด, ํ์ฌ ์์ฑ๋์ด์๋ Mock, Spy๋ค์ ์ฃผ์ ํด์ ๋ง๋ค์ด์ค๋ค.
์ผ์ข ์ Mock์ ์ํ @Autowired๋ผ๊ณ ์๊ฐํ๋ฉด๋๋ค. ๋ค๋ง @Mock์ด ์๋ค๊ณ ํด์ ์ค์ ๊ฐ์ฒด๊ฐ ์ฃผ์ ๋์ง๋ ์๋๋ค.
class Item{...} class Order{ private Item item; public Order(Item item) { this.item = item; } }
public class EX3_Mockito_InjectMocks { @Mock Item item; @InjectMocks // mock(Item.class)๊ฐ ์ฃผ์
๋๋ค. Order order; @Test void injectMocks_Annotation() throws Exception{ MockitoAnnotations.initMocks(this); when(item.isSameName("Book")).thenReturn(true); assertThat(order.itemCheck("Book")).isEqualTo("ํด๋น ์์ดํ
์ด ์ฃผ๋ฌธ๋์์ต๋๋ค."); assertThat(order.itemCheck("Movie")).isEqualTo("์ฃผ๋ฌธ๋ ์์ดํ
์ด ์๋๋๋ค."); } }
์ฐธ๊ณ ๋ก ์๋์ ๊ฐ์ด Mockito.Annotations.openMocks(this) ๋ฅผ ์ด์ฉํด์ @Mock์ ์ฃผ์ ๋ฐ๋ ๋ฐฉ๋ฒ๋ ์๋ค.
* ์ฐธ๊ณ ๋ก ์๋ initMocks()์๋๋ฐ ์ด๋ฆ์ด openMocks๋ก ๋ฐ๋์๋ค. ์ข ๋ ๋ช ํํ ์๋ฏธ๋ก ๋ฐ๋๋ฏ.
public class EX3_Mockito_InjectMocks { @Mock Item item; @Test void non_injectMocks_Annotation() throws Exception{ MockitoAnnotations.openMocks(this); // Mock ๊ฐ์ฒด ์ปจํ
์ด๋ ์ฌ์ฉ final Order order = new Order(item); // item์ Mock ๊ฐ์ฒด ์ปจํ
์ด๋์์ ๊บผ๋ด์์ ๋ฑ๋ก when(item.isSameName("Book")).thenReturn(true); assertThat(order.itemCheck("Book")).isEqualTo("ํด๋น ์์ดํ
์ด ์ฃผ๋ฌธ๋์์ต๋๋ค."); assertThat(order.itemCheck("Movie")).isEqualTo("์ฃผ๋ฌธ๋ ์์ดํ
์ด ์๋๋๋ค."); } }
# 15. Ignore Stubbing
๋ณดํต verifyNoMoreInteractions(mock);๋ฅผ ์ธ ๋ ์ข ์ข ์ฌ์ฉํ๋ค.
verifyNoMoreInteractions(mock); ๋ ํด๋น Mock์ ๋ชจ๋ ์คํฐ๋น์ ์ฌ์ฉํ๋์ง ํ์ธํ๋ ๋ฉ์๋์ด๋ค.
@Test void ignore_stubbing() throws Exception{ final List<String> mock = mock(List.class); when(mock.get(0)).thenReturn("Hello"); final String s = mock.get(0); ignoreStubs(mock); // verify(mock).get(0); ํ์ง ์์๋ ํต๊ณผ!. ignore๊ฐ ๋จ์ ์คํฐ๋น์ ๋ค ์ ๊ฑฐํด์ค. verifyNoMoreInteractions(mock); }
# 16. Mocking details
Mock ๊ฐ์ฒด์ ์ธ๋ถ์ฌํญ๋ค์ ํ ์คํธํ ๋ ์ฌ์ฉ๋๋ค. ๊ฑฐ์ ์ฌ์ฉํ ์ผ์ ์๊ธดํ์ง๋ง ์์ ๋ฅผ ํ๋ฒ ์ดํด๋ณด์.
@Mock Product mockedProduct; @Test void mocking_details() throws Exception { MockitoAnnotations.openMocks(this); // mock ์ปจํ
์ด๋๋ฅผ ๋ถ๋ฌ์จ๋ค. // ์ํ๋ mock์ ๋ํ MockingDetails ๊ฐ์ฒด ์์ฑ final MockingDetails mockingDetails = mockingDetails(mockedProduct); // 1. ํด๋น Mock์ด mock, spy์ธ์ง ํ์ธ ๊ฐ๋ฅํ๋ค. final boolean isMock = mockingDetails.isMock(); assertThat(isMock).isTrue(); final boolean isSpy = mockingDetails.isSpy(); assertThat(isSpy).isFalse(); // 2. Mocking ๊ฐ์ฒด์ ์ด๋ฆ(mockedProduct)์ ์๋ณธ๊ฐ์ฒด์ ํ์
๊ฒ์ฆ๋ ๊ฐ๋ฅ. final MockName mockName = mockingDetails.getMockCreationSettings().getMockName(); assertThat(mockName.toString()).isEqualTo("mockedProduct"); final Class<?> typeToMock = mockingDetails.getMockCreationSettings().getTypeToMock(); assertThat(typeToMock).isSameAs(Product.class); // 3. ํด๋น Mock์ด ๊ฐ์ง ๋ฉ์๋๊ฐ ๋ด๊ฐ ์๊ฐํ๋ ๋ฉ์๋๊ฐ ๋ง๋์ง ๋น๊ต๊ฐ๋ฅ. mockedProduct.setPrice(10000); final Method setPrice = typeToMock.getMethod("setPrice", int.class); mockingDetails.getInvocations().forEach( invocation -> assertThat(invocation.getMethod()).isEqualTo(setPrice) ); // 4. ์คํฐ๋น๋ ๊ฐ๋ค์ ์ ๋ณด๋ ๊ฐ์ ธ์ฌ ์ ์์. when(mockedProduct.getName()).thenReturn("Dexter"); final Method getName = typeToMock.getMethod("getName"); mockingDetails.getStubbings().forEach(stubbing -> { final Method method = stubbing.getInvocation().getMethod(); assertThat(method).isEqualTo(getName); } ); }
#17 Custom Verify Fail-Message
verify ์คํจ ์ ์ํ๋ ๋ฉ์์ง๋ฅผ ์ปค์คํ ํ ์ ์์ต๋๋ค.
@Test void custom_verify_failure_message() throws Exception{ verify(mock, description("This will print on failure")).someMethod(); final List<String> mockedList = mock(List.class); // times()๋ฑ๊ณผ ํจ๊ป ์ฌ์ฉํ๋ค๋ฉด ์๋์ ๊ฐ์ด ๋ฉ์๋ ์ฒด์ด๋์ผ๋ก ์ฐ๋ฉด ๋ฉ๋๋ค. verify(mockedList, times(2).description("ํด๋น ๋ฉ์๋๋ ๋๋ฒ ํธ์ถ๋์ด์ผ ํ๋ค.")).get(0); }
----------
์ด์ ๋๋ฉด ์ถฉ๋ถํ ๊ฒ ๊ฐ๋ค.
๋ ๊ถ๊ธํ ๋ด์ฉ์ด ์๋ค๋ฉด ๊ณต์๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ์.
๋ธ๋ก๊ทธ์ ์ ๋ณด
JiwonDev
JiwonDev