ํ ์คํธ ๋ชจํน Mockito ๋ผ์ด๋ธ๋ฌ๋ฆฌ
by JiwonDev2021.09.07 - [Backend/Java] - ๋ค์ํ ํ ์คํธ์ Test Double (+Mockist)
# 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);
}
----------
์ด์ ๋๋ฉด ์ถฉ๋ถํ ๊ฒ ๊ฐ๋ค.
๋ ๊ถ๊ธํ ๋ด์ฉ์ด ์๋ค๋ฉด ๊ณต์๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ์.
'๐ฑBackend > Java' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Mapstruct(DTO Mapper ๋ผ์ด๋ธ๋ฌ๋ฆฌ) (0) | 2022.01.20 |
---|---|
๋ JPA๋ฅผ ์ ๋๋ก ์๊ณ ์ฐ๋ ๊ฒ์ธ๊ฐ (์ ๋ฆฌ์ค..) (0) | 2021.11.17 |
[์ฝ๋๋ถ์๋๊ตฌ]# 3 SonarLint, SonarQube (0) | 2021.08.21 |
[์ฝ๋๋ถ์๋๊ตฌ]#2 Jacoco ์ ์ฉํ๊ธฐ (0) | 2021.08.21 |
[์ฝ๋๋ถ์ ๋๊ตฌ]# 1 ์ฝ๋ ์ปค๋ฒ๋ฆฌ์ง๋? (0) | 2021.08.21 |
๋ธ๋ก๊ทธ์ ์ ๋ณด
JiwonDev
JiwonDev