mockito使用教程


一、什么是 Mock 測試

Mock 測試就是在測試過程中,對於某些不容易構造(如 HttpServletRequest 必須在Servlet 容器中才能構造出來)或者不容易獲取比較復雜的對象(如 JDBC 中的ResultSet 對象),用一個虛擬的對象(Mock 對象)來創建以便測試的測試方法。Mock 最大的功能是幫你把單元測試的耦合分解開,如果你的代碼對另一個類或者接口有依賴,它能夠幫你模擬這些依賴,並幫你驗證所調用的依賴的行為。
先來看看下面這個示例:

 


從上圖可以看出如果我們要對A進行測試,那么就要先把整個依賴樹構建出來,也就是BCDE的實例。
一種替代方案就是使用mocks

 


從圖中可以清晰的看出
mock對象就是在調試期間用來作為真實對象的替代品。
mock測試就是在測試過程中,對那些不容易構建的對象用一個虛擬對象來代替測試的方法就叫mock測試。

二、Mockito是什么

Mockito 是一個流行 mock 框架,可以和JUnit結合起來使用。Mockito 允許你創建和配置 mock 對象。使用Mockito可以明顯的簡化對外部依賴的測試類的開發。

I notice that Mockito was voted "the best mock framework for Java" on Stackoverflow.

 

三、使用方法

1、添加Maven依賴

<!-- mockito -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.8.5</version>
<scope>test</scope>
</dependency>

 

如果使用springboot的話,有自帶可以不用引入

 

2、建議靜態導入會使代碼更簡潔

import static org.mockito.Mockito.*;
import static org.junit.Assert.*;

3、例子

3.1 創建一個mock對象,並校驗

@Test
public void Demo1() {
// mock creation 創建mock對象
List mockedList = mock(List.class);

//using mock object 使用mock對象
mockedList.add("one");
mockedList.clear();

//verification 驗證
verify(mockedList).add("one");
verify(mockedList).clear();
}

 

3.2 使用測試樁
默認情況下,所有的函數都有返回值。mock函數默認返回的是null,一個空的集合或者一個被對象類型包裝的內置類型,例如0、false對應的對象類型為Integer、Boolean;
測試樁函數可以被覆寫 : 例如常見的測試樁函數可以用於初始化夾具,但是測試函數能夠覆寫它。請注意,覆寫測試樁函數是一種可能存在潛在問題的做法;
一旦測試樁函數被調用,該函數將會一致返回固定的值;
上一次調用測試樁函數有時候極為重要-當你調用一個函數很多次時,最后一次調用可能是你所感興趣的。

@Test
public void Demo2() {
// 你可以mock具體的類型,不僅只是接口
LinkedList mockedList = mock(LinkedList.class);

// 測試樁(可以使用連續調用)
when(mockedList.get(0)).thenReturn("first");
//when(mockedList.get(0)).thenReturn("first").thenReturn("second");
when(mockedList.get(1)).thenThrow(new RuntimeException());

// 輸出“first”
System.out.println(mockedList.get(0));
// 連續調用則輸出“second”
//System.out.println(mockedList.get(0));

// 拋出異常
System.out.println(mockedList.get(1));

// 因為get(999) 沒有打樁,因此輸出null
System.out.println(mockedList.get(999));

// 驗證get(0)被調用的次數
verify(mockedList).get(0);
}

 

3.3 驗證函數的確切、最少、從未調用次數

@Test
public void Demo3() {
// 你可以mock具體的類型,不僅只是接口
LinkedList mockedList = mock(LinkedList.class);
//using mock
mockedList.add("once");

mockedList.add("twice");
mockedList.add("twice");

mockedList.add("three times");
mockedList.add("three times");
mockedList.add("three times");

// 下面的兩個驗證函數效果一樣,因為verify默認驗證的就是times(1)
verify(mockedList).add("once");
verify(mockedList, times(1)).add("once");

// 驗證具體的執行次數
verify(mockedList, times(2)).add("twice");
verify(mockedList, times(3)).add("three times");

// 使用never()進行驗證,never相當於times(0)
verify(mockedList, never()).add("never happened");

// 使用atLeast()/atMost()
verify(mockedList, atLeastOnce()).add("three times");

// mockedList.add("five times");
// mockedList.add("five times");
// mockedList.add("five times");
// mockedList.add("three times");
// mockedList.add("three times");
// mockedList.add("three times");
//最少or最多
verify(mockedList, atLeast(2)).add("five times");
verify(mockedList, atMost(5)).add("three times");

}

 

3.4 模擬異常

@Test
public void Demo4() {

LinkedList mockedList = mock(LinkedList.class);
doThrow(new RuntimeException()).when(mockedList).clear();

// 調用這句代碼會拋出異常
mockedList.clear();
}

 

3.5 驗證執行執行順序

@Test
public void Demo5() {
// A. 驗證mock一個對象的函數執行順序
List singleMock = mock(List.class);

singleMock.add("was added first");
singleMock.add("was added second");
singleMock.contains("111");
singleMock.isEmpty();
singleMock.remove(0);
singleMock.get(0);

// 為該mock對象創建一個inOrder對象
InOrder inOrder = inOrder(singleMock);

// 確保add函數首先執行的是add("was added first"),然后才是add("was added second")
inOrder.verify(singleMock).add("was added first");
inOrder.verify(singleMock).add("was added second");
inOrder.verify(singleMock).contains("111");
inOrder.verify(singleMock).isEmpty();
inOrder.verify(singleMock).remove(0);
inOrder.verify(singleMock).get(0);

// // B .驗證多個mock對象的函數執行順序
// List firstMock = mock(List.class);
// List secondMock = mock(List.class);
//
// firstMock.add("was called first");
// secondMock.add("was called second");
//
// // 為這兩個Mock對象創建inOrder對象
// InOrder inOrder = inOrder(firstMock, secondMock);
//
// // 驗證它們的執行順序
// inOrder.verify(firstMock).add("was called first");
// inOrder.verify(secondMock).add("was called second");

// Oh, and A + B can be mixed together at will
}

 

3.6 spy
然而很多時候,你希望達到這樣的效果:除非指定,否者調用這個對象的默認實現,同時又能擁有驗證方法調用的功能。這正好是spy對象所能實現的效果。創建一個spy對象,以及spy對象的用法介紹如下:

@Test
public void Demo6() {

List list = new LinkedList();
List spy = spy(list);

// 你可以為某些函數打樁
when(spy.size()).thenReturn(100);

// 通過spy對象調用真實對象的函數
spy.add("one");
spy.add("two");

// 輸出第一個元素
System.out.println(spy.get(0));

// 因為size()函數被打樁了,因此這里返回的是100
System.out.println(spy.size());

// 交互驗證
verify(spy).add("one");
verify(spy).add("two");
}

 

3.7 在監控對象上使用when(Object)來進行打樁是不可能或者不切實際的。
因此,當使用監控對象時請考慮doReturn|Answer|Throw()函數族來進行打樁。

@Test
public void Demo7(){
List list = new LinkedList();
List spy = spy(list);

// 不可能 : 因為當調用spy.get(0)時會調用真實對象的get(0)函數,此時會發生IndexOutOfBoundsException異常,因為真實List對象是空的
//when(spy.get(0)).thenReturn("foo");

// 你需要使用doReturn()來打樁
doReturn("foo").when(spy).get(anyInt());

System.out.println(spy.get(0));
// doThrow(new RuntimeException()).when(spy).get(anyInt());
// System.out.println(spy.get(0));
}

 

3.8使用注解形式來模擬測試對象,必須在初始化fields (領域),有2種方式初始化:

@RunWith(@MockitoJUnitRunner.class) 標注 JUnit 測試類
@Before 之前調用 MockitoAnnotations.initMocks(Object)
@Before
public void init(){
MockitoAnnotations.initMocks(this);
}

 

或者

@RunWith(MockitoJUnitRunner.class)

 

這里補充一下幾個注解的作用

一般項目測試是用@RunWith(SpringRunner.class)

這個運行器來啟動的,有些教程使用的是SpringJUnit4ClassRunner這個運行器,我們可以查看源碼,其實是同一個東西,

public final class SpringRunner extends SpringJUnit4ClassRunner {
    public SpringRunner(Class<?> clazz) throws InitializationError {
        super(clazz);
    }
}

 

3.9 參數捕獲

ArgumentCaptor類允許我們在verification期間訪問方法的參數。得到方法的參數后我們可以使用它進行測試。
ArgumentCaptor是一個能夠捕獲參數值的特殊參數匹配器。捕獲一個 Mock 對象的方法調用所傳遞的參數

@Test
public void testCaptureArgument() {
    List<String> list = Arrays.asList("1", "2");
    List mockedList = mock(List.class);
    ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class);
    mockedList.addAll(list);
    verify(mockedList).addAll(argument.capture());

    assertEquals(2, argument.getValue().size());
    assertEquals(list, argument.getValue());
}

 

3.10 RETURNS_SMART_NULLS 和 RETURNS_DEEP_STUBS

RETURNS_SMART_NULLS

(返回默認值-查看源碼: ReturnsMoreEmptyValues)
RETURNS_SMART_NULLS
在創建mock對象時,有的方法我們沒有進行stubbing,所以在調用的時候有時會返回Null這樣在進行處理時就很可能拋出NullPointerException。如果通過RETURNS_SMART_NULLS參數來創建的mock對象在調用沒有stubbed的方法時他將返回SmartNull。例如:返回類型是String它將返回空字符串””;是int,它將返回0;如果是List,它會返回一個空的List。另外,在堆棧中可以看到SmartNull的友好提示。 


由於使用了RETURNS_SMART_NULLS參數來創建mock對象,所以在執行下面的操作時將不會拋出NullPointerException異常,另外堆棧也提示了相關的信息“SmartNull returned by unstubbed get() method on mock”。 

@Test
public void returnsSmartNullsTest() {
    //List mock = mock(List.class);
    List mock = mock(List.class, RETURNS_SMART_NULLS);
    System.out.println(mock.get(0));

    // 使用RETURNS_SMART_NULLS參數創建的mock對象,不會拋出NullPointerException異常。
    // 另外控制台窗口會提示信息“SmartNull returned by unstubbed get() method on mock”
    System.out.println(mock.toArray().length);
}

 

RETURNS_DEEP_STUBS

(判斷是否需要返回默認值,否則創建mock對象 ReturnsDeepStubs)
RETURNS_DEEP_STUBS參數程序會自動進行mock所需的對象,方法deepstubsTest和deepstubsTest2是等價的

public class FakeEntity {
    private UserFakeEntity userFakeEntity;

    public UserFakeEntity getUserFakeEntity() {
        return userFakeEntity;
    }

    public void setUserFakeEntity(UserFakeEntity userFakeEntity) {
        this.userFakeEntity = userFakeEntity;
    }
}


@Test
public void deepstubsTest() {
    //FakeEntity fakeEntity = mock(FakeEntity.class);//這樣會NullPointerException
    FakeEntity fakeEntity = mock(FakeEntity.class, RETURNS_DEEP_STUBS);
    when(fakeEntity.getUserFakeEntity().getName()).thenReturn("Beijing");
    System.out.println(fakeEntity.getUserFakeEntity().getName());
    assertEquals("Beijing", fakeEntity.getUserFakeEntity().getName());
}

@Test
public void deepstubsTest2() {
    FakeEntity fakeEntity = mock(FakeEntity.class);
    UserFakeEntity userFakeEntity = mock(UserFakeEntity.class);

    when(fakeEntity.getUserFakeEntity()).thenReturn(userFakeEntity);
    when(userFakeEntity.getName()).thenReturn("Beijing");
    System.out.println(fakeEntity.getUserFakeEntity().getName());
    assertEquals("Beijing", fakeEntity.getUserFakeEntity().getName());
}

 

四、實戰使用

4.1登錄的例子

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.boot.test.context.SpringBootTest;

import static org.mockito.Mockito.*;


//基於MockitoJUnitRunner的運行器
@RunWith(MockitoJUnitRunner.class)
//@RunWith(SpringRunner.class)
@SpringBootTest
public class MockLoginTest {


//自動將模擬對象或偵查域注入到被測試對象中。
//對被測類中@Autowired的對象,用@Mocks標注;對被測類自己,用@InjectMocks標注
@InjectMocks
private UserServiceImpl userService;

@Mock
private UserRepository userDao;

@Mock
private BargainBlackListService bargainBlackListService;

@Mock
private DistributionUserService distributionUserService;

@Mock
private AuthorityService authorityService;

@Before
public void init(){
MockitoAnnotations.initMocks(this);
}

@Test
public void loginTest() {
//模擬方法動作
//spyUserService在調用getUserEntity方法的時候,指定返回事先定義好的userEntity
UserService spyUserService = spy(userService);
UserEntity userEntity = new UserEntity();
userEntity.setUserName("ppp");
doReturn(userEntity).when(spyUserService).getUserEntity(anyString());
when(bargainBlackListService.checkBlackList(anyString())).thenReturn(true);
UserInfoDTO userInfoDTO = new UserInfoDTO();
userInfoDTO.setUserName("ppp");
userInfoDTO.setId("2c95808a644ade6801644ae37f730000");
spyUserService.updateUserInfo("0520b1d9-9806-4ec1-a095-dfcd8bea8fd6", userInfoDTO);
}


}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM