Mock์ "๋ชจ์, ๊ฐ์ง์"๋ผ๋ ๋ป์ผ๋ก, ํ
์คํธ์ ์ค์ ๊ฐ์ฒด์ ๋์ผํ ๋ชจ์ ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด ํ
์คํธ ํจ์ฉ์ฑ์ ๋์ด๊ธฐ ์ํด ์ฌ์ฉํ๋ค.
๋ชจ์ ๊ฐ์ฒด (Mock Object)๋ ์ฃผ๋ก ๊ฐ์ฒด ์งํฅ ํ๋ก๊ทธ๋๋ฐ ์ผ๋ก ๊ฐ๋ฐํ ํ๋ก๊ทธ๋จ์ ํ
์คํธ ํ ๊ฒฝ์ฐ ํ
์คํธ๋ฅผ ์ํํ ๋ชจ๋๊ณผ ์ฐ๊ฒฐ๋๋ ์ธ๋ถ์ ๋ค๋ฅธ ์๋น์ค๋ ๋ชจ๋๋ค์ ์ค์ ์ฌ์ฉํ๋ ๋ชจ๋์ ์ฌ์ฉํ์ง ์๊ณ ์ค์ ์ ๋ชจ๋์ "ํ๋ด"๋ด๋ "๊ฐ์ง" ๋ชจ๋์ ์์ฑํ์ฌ ํ
์คํธ์ ํจ์ฉ์ฑ์ ๋์ด๋๋ฐ ์ฌ์ฉํ๋ ๊ฐ์ฒด ์ด๋ค.
์ํค ๋ฐฑ๊ณผ-๋ชจ์ ๊ฐ์ฒด
Mockito๋ Mock ๊ฐ์ฒด๋ฅผ ์ง์ํ๋ ํ
์คํธ ํ๋ ์์ํฌ๋ก, Mockito๋ฅผ ํ์ฉํ์ฌ ๊ฐ์ง ๊ฐ์ฒด์ ์ํ๋ ๊ฒฐ๊ณผ๋ฅผ Stubํ์ฌ ๋จ์ ํ
์คํธ๋ฅผ ์งํํ ์ ์๋ค.
ํ๊ฒฝ ์ค์
์คํ๋ง ๋ถํธ 2.0์ดํ์๋ spring-boot-starter-test
dependency์ ํฌํจ๋์ด ์๋ค.
gradle
Copy repositories { mavenCentral() }
dependencies { testImplementation "org.mockito:mockito-core:4.0.0" }
์์ํ๊ธฐ
๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์ ํ Mockito๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋ ํ
์คํธ ํด๋์ค ์์ @ExtendWith(MockitoExtension.class)
๋ฅผ ์ถ๊ฐํด์ค์ผํ๋ค.
Copy @ ExtendWith ( MockitoExtension . class )
public class mockTest {
}
Mock ์์ฑ
mock ์์ฑ๊ณผ ๊ด๋ จ๋ ์ด๋
ธํ
์ด์
์ @Mock
, @Spy
, @InjectMock
์ด ์๋ค.
mock ์์ฑ ๊ด๋ จ ํ
์คํธ๋ฅผ ์ํด ์์๋ก ItemService
๋ฅผ ์์ฑํด์ฃผ์๋ค.
Copy public class ItemService {
public Item getItem () {
return new Item( "0000000001" , "10" ) ;
}
public boolean isOnlineMallItem () {
return true ;
}
}
@Mock
@Mock
์ Mock ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด ๋ฐํํด์ฃผ๋ ์ด๋
ธํ
์ด์
์ด๋ค. ์์ฑํ mock ๊ฐ์ฒด๋ stubbing์ ํด์ฃผ์ง ์๊ณ , ํธ์ถํ๊ฒ ๋๋ฉด primitive type์ 0, ์ฐธ์กฐํ์ null์ ๋ฐํํ๋ค.
Copy @ ExtendWith ( MockitoExtension . class )
public class MockTest {
@ Mock
ItemService itemService;
@ Test
void ์ฐธ์กฐํ์
_NOT_STUBBING_ํ
์คํธ () {
assertThat( itemService . getItem()) . isNull ();
}
@ Test
void ๊ธฐ๋ณธํ์
_NOT_STUBBING_ํ
์คํธ () {
assertThat( itemService . isOnlineMallItem()) . isFalse ();
}
}
@Spy
@Spy
๋ก ์์ฑํ mock ๊ฐ์ฒด๋ ์ค์ ๊ฐ์ฒด์ด๋ฉฐ, ๋ฉ์๋ ์คํ ์ stubbing์ ํ์ง ์์ผ๋ฉด ๊ธฐ์กด ๋ก์ง์ ์คํํ ๊ฐ์, stubbing์ ํ ๊ฒฝ์ฐ์๋ ๊ทธ ๊ฐ์ ๋ฐํํ๋ค.
Copy @ ExtendWith ( MockitoExtension . class )
public class SpyTest {
@ Spy
ItemService itemService;
@ Test
void NOT_STUBBING_ํ
์คํธ () {
// given
// when
Item item = itemService . getItem ();
// then
assertThat(item) . isNotNull ();
assertThat( item . getItemDivCd()) . isEqualTo ( "10" );
assertThat( item . getItemId()) . isEqualTo ( "0000000001" );
}
@ Test
void STUBBING_ํ
์คํธ () {
// given
when( itemService . getItem())
. thenReturn ( new Item( "0000000002" , "20" ) );
// when
Item item = itemService . getItem ();
// then
assertThat(item) . isNotNull ();
assertThat( item . getItemDivCd()) . isEqualTo ( "20" );
assertThat( item . getItemId()) . isEqualTo ( "0000000002" );
}
}
@InjectMocks
์ @Mock
ํน์ @Spy
๋ก ์์ฑํ ๊ฐ์ฒด๋ฅผ ์๋์ผ๋ก ์ฃผ์
ํด์ฃผ๋ ์ด๋
ธํ
์ด์
์ด๋ค. ํด๋น ์ด๋
ธํ
์ด์
์ ์ฌ์ฉํด ์ฃผ์
๋ฐ์ ์ ์๋ ํํ๋ Constructure, Property Setter, Field Injection์ด ์๋ค.
Stub
Stub(Stubbing)์ ํ ๋ง, ๋จ์ ๋ถ๋ถ, ๊ฝ์ด, ๋ชฝ๋น์ฐํ์ด๋ผ๋ ์๋ฏธ๋ฅผ ๊ฐ์ง๊ณ ์์ผ๋ฉฐ ๊ฐ์ง ๊ฐ์ฒด๊ฐ ๋ง์น ์ค์ ๋ก ๋์ํ๋ ๊ฒ์ฒ๋ผ ๋ณด์ด๋๋ก ๋ง๋ค์ด๋์ ๊ฒ์ด๋ค.
Test stubs provide canned answers to calls made during the test, usually not responding at all to anything outside whatโs programmed in for the test.
wikipedia
์ฆ, stub์ ๋ง๋ค์ด์ง mock ๊ฐ์ฒด๊ฐ ์ด๋ค ๊ฐ์ ๋ฐํํ ์ง๋ฅผ ์ ์ํ๋ ๊ฒ์ด๋ค.
OngoingStubbing
Mockito์์๋ when ๋ฉ์๋๋ฅผ ์ด์ฉํด OngoingStubbing์ ์ง์ํ๊ณ ์๋ค. OngoingStubbing์ when์ ๋ฃ์ ๋ฉ์๋์ ๋ฆฌํด๊ฐ์ ์ ์ํด์ฃผ๋ ๋ฉ์๋์ด๋ค.
Stubbingํ ๋ฉ์๋ ํธ์ถ ํ ์ด๋ค ๊ฐ์ฒด๋ฅผ ๋ฐํํ ์ง ์ ์
Stubbingํ ๋ฉ์๋ ํธ์ถ ํ ์ด๋ค Exception์ Throwํ ์ง ์ ์
Stubbingํ ๋ฉ์๋ ํธ์ถ ํ ์ด๋ค ์์
์ ํ ์ง custom ์ ์
์ด ๋ฉ์๋๋ ์ฌ์ฉํ์ง ๋ง๊ณ , thenReturn, thenThrow ์ฌ์ฉ ๊ถ์ฅ
Copy when({stubbingํ ๋ฉ์๋}) . {OngoingStubbing ๋ฉ์๋};
Copy @ ExtendWith ( MockitoExtension . class )
public class OngoingStubbingTest {
@ Mock
ItemService itemService;
@ Test
void thenReturn_ํ
์คํธ () {
Item item = new Item( "0000000002" , "20" ) ;
when( itemService . getItem())
. thenReturn (item);
assertThat( itemService . getItem()) . isEqualTo (item);
}
@ Test
void thenThrow_ํ
์คํธ () {
when( itemService . getItem())
. thenThrow ( new IllegalArgumentException() );
assertThrows( IllegalArgumentException . class , () -> {
itemService . getItem();
}) ;
}
@ Test
void thenCallRealMethod_ํ
์คํธ () {
when( itemService . getItem())
. thenCallRealMethod ();
assertThat( itemService . getItem()) . isNotNull ();
assertThat( itemService . getItem() . getItemId()) . isEqualTo ( "0000000001" );
}
}
๋ฉ์๋ ์ฒด์ด๋์ผ๋ก ์ฌ์ฉํ๋ฉด, ํธ์ถ์๋ง๋ค ๋ค๋ฅธ Stubbing์ ํธ์ถํ ์ ์๋ค.
Copy @ Test
void ๋ฉ์๋์ฒด์ด๋ _ํ
์คํธ() {
Item item = new Item( "0000000002" , "20" ) ;
when( itemService . getItem())
. thenReturn (item)
. thenThrow ( new RuntimeException() );
assertThat( itemService . getItem()) . isEqualTo (item);
assertThrows( RuntimeException . class , () -> {
itemService . getItem();
}) ;
}
Stubber
Stubber ๋ฉ์๋๋ ๋ฐ๋์ Stubbing์ด ์คํ๋์ผํ๋ ๊ฒฝ์ฐ ์ฌ์ฉํ๋ ๋ฉ์๋์ด๋ค.
Stubbing ๋ฉ์๋ ํธ์ถ ํ ์ด๋ค ํ๋์ ํ ๊ฑด์ง ์ ์
Stubbing ๋ฉ์๋ ํธ์ถ ํ ์ด๋ค Exception์ throwํ ๊ฑด์ง ์ ์
Stubbing ๋ฉ์๋ ํธ์ถ ํ ์์
์ ํ ์ง customํ๊ฒ ์ ์
Stubbing ๋ฉ์๋ ํธ์ถ ํ ์ด๋ค ํ๋๋ ํ์ง ์๊ฒ ์ ์
Copy {Stubber ๋ฉ์๋} . when ({Stubbingํ ํด๋์ค}) . {Stubbing ํ ๋ฉ์๋}
Copy @ ExtendWith ( MockitoExtension . class )
public class StubberTest {
@ Mock
ItemService itemService;
@ Test
void doReturn_ํ
์คํธ () {
List list = new LinkedList() ;
List spyList = spy(list) ;
doReturn( "test" ) . when (spyList) . get ( 0 );
assertThat( spyList . get( 0 )) . isEqualTo ( "test" );
Item item = new Item( "0000000003" , "30" ) ;
doReturn(item)
. when (itemService)
. getItem ();
assertThat( itemService . getItem()) . isEqualTo (item);
}
@ Test
void doThrow_ํ
์คํธ () {
doThrow( new RuntimeException()) . when (itemService) . getItem ();
assertThatThrownBy(() -> itemService . getItem()) . isInstanceOf ( RuntimeException . class );
assertThrows( RuntimeException . class , () -> {
itemService . getItem();
}) ;
}
}
๊ฒ์ฆ
verify()
๋ฅผ ์ด์ฉํ์ฌ Stubbingํ ๋ฉ์๋๊ฐ ์ ์์ ์ผ๋ก ์คํ๋๋์ง ๊ฒ์ฆํ ์ ์๋ค.
Copy verify( T mock , VerificationMode mode)
๋ช ๋ฒ์ด ํธ์ถ๋๋์ง ๊ฒ์ฆ
ํ ๋ฒ๋ ํธ์ถ๋์ง ์์๋์ง ๊ฒ์ฆ
์ต์ ํ ๋ฒ์ ํธ์ถ๋๋์ง ๊ฒ์ฆ
์ต์ n ๋ฒ์ด ํธ์ถ๋๋์ง ๊ฒ์ฆ
์ต๋ ํ ๋ฒ์ด ํธ์ถ๋๋์ง ๊ฒ์ฆ
์ต๋ n ๋ฒ์ด ํธ์ถ๋๋์ง ๊ฒ์ฆ
n๋ฒ์ด ํธ์ถ๋๋์ง ๊ฒ์ฆ (InOrder๋ ๊ฐ์ด ์ฌ์ฉํด์ผ ํจ)
ํด๋น ๊ฒ์ฆ ๋ฉ์๋๋ง ์คํ๋๋์ง ๊ฒ์ฆ
n ms ์ด์ ๊ฑธ๋ฆฌ๋ฉด Fail ๊ทธ๋ฆฌ๊ณ ๋ฐ๋ก ๊ฒ์ฆ ์ข
๋ฃ
n ms ์ด์ ๊ฑธ๋ฆฌ๋์ง ํ์ธ
timeout๊ณผ ๋ค๋ฅด๊ฒ ์๊ฐ์ด ์ง๋๋ ๋ฐ๋ก ๊ฒ์ฆ ์ข
๋ฃ๊ฐ ๋์ง ์์
์คํจํ ๊ฒฝ์ฐ ๋์ฌ ๋ฌธ๊ตฌ
Copy @ ExtendWith ( MockitoExtension . class )
public class VerifyTest {
@ Mock
ItemService itemService;
@ Test
void times_ํ
์คํธ () {
itemService . getItem ();
itemService . getItem ();
// ๋ช๋ฒ ํธ์ถ๋๋์ง ๊ฒ์ฆ
verify(itemService , times( 2 )) . getItem ();
}
@ Test
void never_ํ
์คํธ () {
// ํ ๋ฒ๋ ํธ์ถ๋์ง ์์๋์ง ๊ฒ์ฆ
verify(itemService , never()) . getItem ();
}
@ Test
void atLeastOnce_ํ
์คํธ () {
itemService . getItem ();
itemService . getItem ();
itemService . getItem ();
itemService . getItem ();
// ์ต์ 1๋ฒ ํธ์ถ๋๋์ง ๊ฒ์ฆ
verify(itemService , atLeastOnce()) . getItem ();
}
@ Test
void atLeast_ํ
์คํธ () {
itemService . getItem ();
itemService . getItem ();
itemService . getItem ();
itemService . getItem ();
// ์ต์ N๋ฒ ํธ์ถ๋๋์ง ๊ฒ์ฆ
verify(itemService , atLeast( 4 )) . getItem ();
}
@ Test
void atMostOnce_ํ
์คํธ () {
itemService . getItem ();
// ์ต๋ N๋ฒ ํธ์ถ๋๋์ง ๊ฒ์ฆ
verify(itemService , atMostOnce()) . getItem ();
}
@ Test
void atMost_ํ
์คํธ () {
itemService . getItem ();
itemService . getItem ();
itemService . getItem ();
itemService . getItem ();
// ์ต๋ N๋ฒ ํธ์ถ๋๋์ง ๊ฒ์ฆ
verify(itemService , atMost( 4 )) . getItem ();
}
@ Test
void inOrder_calls_ํ
์คํธ () {
itemService . getItem ();
itemService . getItem ();
itemService . getItem ();
itemService . deleteItem ();
InOrder inOrder = inOrder(itemService) ;
inOrder . verify (itemService , calls( 3 ) ) . getItem ();
inOrder . verify (itemService) . deleteItem ();
}
}
์ฐธ๊ณ