mockito開始使用mockito


備註

Mockito是一個java Mocking框架,旨在通過使用它的簡單API提供編寫清晰可讀單元測試的能力。它與其他模擬框架不同,它保留了大多數其他框架使用的expect-run-verify模式。

相反,它只知道模擬(非最終)類和接口的一種方法,並允許基於靈活的參數匹配器驗證和存根。

當前版本1.10.19最好使用maven獲得

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>1.10.19</version>
</dependency>

或者是gradle

repositories { jcenter() }
dependencies { testCompile "org.mockito:mockito-core:1.+" }

版本2仍處於測試階段。

版本

Maven Central 發行說明發布日期
2.1.0 的Mockito核心 變化 2016年10月4日
19年1月10日 的Mockito核心 變化 2014-12-31

向mock對象添加行為

Mockito.when(mock.returnSomething()).thenReturn("my val");

mock.returnSomething(); // returns "my val"
mock.returnSomething(); // returns "my val" again
mock.returnSomething(); // returns "my val" again and again and again...
 

如果你想在第二次調用時獲得不同的值,你可以在thenReturn方法中添加想要的返回參數:

Mockito.when(mock.returnSomething()).thenReturn("my val", "other val");

mock.returnSomething(); // returns "my val"
mock.returnSomething(); // returns "other val"
mock.returnSomething(); // returns "other val" again
 

如果您將調用方法而不向mock添加行為,則它將返回null:

barMock.mock.returnSomethingElse(); // returns null
 

如果mocked方法有參數,你也應該聲明值:

Mockito.when(mock.returnSomething("param 1")).thenReturn("my val 1");
Mockito.when(mock.returnSomething("param 2")).thenReturn("my val 2");

mock.returnSomething("param 1"); // returns "my val 1"
mock.returnSomething("param 2"); // returns "my val 2"
mock.returnSomething("param 3"); // returns null
 

如果您不關心param值,可以使用Matchers.any():

Mockito.when(mock.returnSomething(Matchers.any())).thenReturn("p1");

mock.returnSomething("param 1"); // returns "p1"
mock.returnSomething("param other"); // returns "p1"
 

要拋出異常使用thenThrow方法:

Mockito.when(mock.returnSomething()).thenThrow(new Exception());

mock.returnSomething(); // throws Exception
 

檢查傳遞給mock的參數

讓我們假設我們有這個類,我們想測試doSmth 方法。在這種情況下,我們想看看參數“val”是否傳遞給foo 。對象foo 被嘲笑。

public class Bar {

    private final Foo foo;

    public Bar(final Foo foo) {
        this.foo = foo;
    }

    public void doSmth() {
        foo.bla("val");
    }
}
 

我們可以使用ArgumentCaptor 實現這一目標:

@Mock
private Foo fooMock;

@InjectMocks
private Bar underTest;

@Captor
private ArgumentCaptor<String> stringCaptor;

@Test
public void should_test_smth() {
    underTest.doSmth();

    Mockito.verify(fooMock).bla(stringCaptor.capture());

    assertThat(stringCaptor.getValue(), is("val"));
}
 

創建由Mockito模擬的對象

創建由Mockito模擬的對像有兩種方法:

  • 通過註釋
  • 通過模擬功能

通過註釋:

使用JUnit測試運行器:

@RunWith(MockitoJUnitRunner.class)
public class FooTest {
    @Mock
    private Bar barMock;

    // ...
}
 

你也可以使用Mockito的JUnit @Rule ,它提供與MockitoJUnitRunner 相同的功能,但不需要@RunWith 測試運行器:

public class FooTest {
    @Rule
    public MockitoRule mockito = MockitoJUnit.rule();        

    @Mock
    private Bar barMock;

    // ...
}
 

如果你不能使用@RunWith@Rule 註釋,你也可以“按手”初始化:

public class FooTest {
    @Mock
    private Bar barMock;

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

    // ...
}
 

通過模擬功能:

public class FooTest {
    private Bar barMock = Mockito.mock(Bar.class);

    // ...
}
 

由於類型擦除,您不能像上面那樣模擬泛型類。您必須模擬基類並顯式轉換為正確的泛型類型:

public class FooTest {
    private Bar<String> genericBarMock = (Bar<String>) Mockito.mock(Bar.class);

    // ...
}
 

安裝和設置

安裝

安裝Mockito的首選方法是使用選擇的構建系統聲明對mockito-core 的依賴。截至2016年7月22日,最新的非beta版本為1.10.19,但已鼓勵2.x遷移到

Maven的

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

搖籃

repositories { jcenter() }
dependencies { testCompile "org.mockito:mockito-core:1.+" }
 

除了Mockito本身之外,還有包含Hamcrest和Objenesis的mockito-all 。它通過Maven主要為螞蟻用戶提供,但Mockito 2.x已經停止發布。


進口

最的設施的Mockito是靜態方法org.mockito.Mockito 。因此,Mockito可以通過這種方式靜態導入到類中:

import static org.mockito.Mockito.*;
 

文檔入口點位於此類的javadoc中。

在對像上模擬一些方法

只需使用mock的spy() 來模擬對象的某些方法。

例如,假設方法類需要一些Web服務才能工作。

public class UserManager {

    List<User> users;        

    public UserManager() {
        user = new LinkedLisk<User>();
    }
    
    public void addUser(User user) {
        if (isValid(user)) {
            user.add(user);
        } else {
            throw new NotValidUserException();
        }
    }
    
    protected boolean isValid(User user) {
        //some online web service to check if user is valid
    }
    
    public int numberOfUsers() {
        return users.size();
    }
}
 

必須測試addUser 方法才能為UserManager 進行有用的測試。但是,這裡找到依賴關係, isValid 需要一個未包含在我們的代碼中的外部Web服務。然後,應該中和這種外部依賴。

在這種情況下,如果只模擬isValid 您將能夠測試其餘的UserManager 方法。

@Test
public void testAddUser() {
    User user = mock(User.class);
    UserManager  manager = spy(new UserManager());
    
    //it forces to manager.isValid to return true
    doReturn(true).when(manager).isValid(anyObject());
    
    manager.addUser(user);
    assertTrue(manager.numberOfUsers(), 1);
} 
 

您可以輕鬆檢查user 無效的方案。

@Test(expectedExceptions = NotValidUserException.class)
public void testNotValidAddUser() {
    User user = mock(User.class);
    UserManager  manager = spy(new UserManager());
    
    //it forces to manager.isValid to return false
    doReturn(false).when(manager).isValid(anyObject());
    
    manager.addUser(user);
} 
 

簡單的最小Mockito測試

此示例顯示了使用模擬ArrayList 的最小Mockito測試:

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

import java.util.ArrayList;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class MockitoTest
{
    @Mock
    ArrayList<String> listMock;

    @Test
    public void testAppend() {
        // configure the mock to return "foobar" whenever "get()" 
        // is called on "listMock" with an int value as parameter
        doReturn("foobar").when(listMock).get(anyInt());            
        String result = listMock.get(0);
        
        assertEquals("foobar", result);
    }
}
 

使用Mockito進行簡單的單元測試

我們要測試的課程是:

public class Service {

    private Collaborator collaborator;

    public Service(Collaborator collaborator) {
        this.collaborator = collaborator;
    }
    
    public String performService(String input) {
        return collaborator.transformString(input);
    }
}
 

它的合作者是:

public class Collaborator {

    public String transformString(String input) {
        return doStuff();
    }

    private String doStuff() {
        // This method may be full of bugs
        . . .
        return someString;
    }

}
 

在我們的測試中,我們希望打破Collaborator 及其bug的依賴性,因此我們將模擬Collaborator

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

import org.junit.Test;

public class ServiceTest {
    @Test
    public void testPerformService() throws Exception {
        // Configure mock
        Collaborator collaboratorMock = mock(Collaborator.class);
        doReturn("output").when(collaboratorMock).transformString("input");

        // Perform the test
        Service service = new Service(collaboratorMock);
        String actual = service.performService("input");
        
        // Junit asserts
        String expected = "output";
        assertEquals(expected, actual);
    }  
}
 

Stubbing void方法

void 方法可以使用doThrow()doAnswer()doNothing()doCallRealMethod()方法系列進行存根

Runnable mock = mock(Runnable.class);

doThrow(new UnsupportedOperationException()).when(mock).run();

mock.run(); // throws the UnsupportedOperationException
 

請注意, void 方法不能使用when(..) 導致編譯器不喜歡void 方法作為參數。

使用Mockito註釋

我們要測試的課程是:

public class Service{

    private Collaborator collaborator;

    public Service(Collaborator collaborator){
        this.collaborator = collaborator;
    }
    
    
    public String performService(String input){
        return collaborator.transformString(input);
    }
}
 

它的合作者是:

public class Collaborator {

    public String transformString(String input){
        return doStuff();
    }

    private String doStuff()
    {
        // This method may be full of bugs
        . . .
        return someString;
    }

}
 

在我們的測試中,我們希望打破Collaborator 及其錯誤的依賴性,因此我們將模擬Collaborator 。使用@Mock 註釋是為每個測試創建不同模擬實例的便捷方法:

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

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.InjectMocks;
import org.mockito.runners.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class ServiceTest {

    @Mock
    private Collaborator collaboratorMock;

    @InjectMocks
    private Service service;
    
    @Test
    public void testPerformService() throws Exception {
        // Configure mock
        doReturn("output").when(collaboratorMock).transformString("input");            

        // Perform the test
        String actual = service.performService("input");
        
        // Junit asserts
        String expected = "output";
        assertEquals(expected, actual);
    }
    
    
    @Test(expected=Exception.class)
    public void testPerformServiceShouldFail() throws Exception {
        // Configure mock
        doThrow(new Exception()).when(collaboratorMock).transformString("input");

        // Perform the test
        service.performService("input");
    }
}
 

Mockito將嘗試按以下順序解決依賴注入:

  1. 基於構造函數的注入 - 使用大多數參數將模擬注入到構造函數中(如果找不到某些參數,則傳遞空值)。如果通過構造函數成功創建了對象,則不會應用其他策略。
  2. 基於定位器的注射 - 模擬按類型注入。如果存在多個相同類型的屬性,則將匹配屬性名稱和模擬名稱。
  3. 直接進樣 - 與基於setter的進樣相同。

請注意,如果上述任何策略失敗,則不會報告失敗。

有關@InjectMocks 最新版本中此機制的更多詳細信息,請參閱最新的@InjectMocks。

驗證對模擬對象的方法調用

要檢查是否在Mockito.verifyMockito.verify 上調用了方法,可以使用Mockito.verify 方法:

Mockito.verify(someMock).bla();
 

在這個例子中,我們斷言在someMock 模擬對someMock 上調用方法bla

您還可以檢查是否使用某些參數調用了方法:

Mockito.verify(someMock).bla("param 1");
 

如果要檢查是否調用方法,可以傳遞其他VerificationMode 參數來verify

Mockito.verify(someMock, Mockito.times(0)).bla();
 

如果你想檢查這個方法被多次調用(在這種情況下我們檢查方法bla 被調用了23次),這也有效:

Mockito.verify(someMock, Mockito.times(23)).bla();
 

這些是VerificationMode 參數的更多示例,可以更多地控制應該調用方法的次數:

Mockito.verify(someMock, Mockito.never()).bla(); // same as Mockito.times(0)

Mockito.verify(someMock, Mockito.atLeast(3)).bla(); // min 3 calls

Mockito.verify(someMock, Mockito.atLeastOnce()).bla(); // same as Mockito.atLeast(1)

Mockito.verify(someMock, Mockito.atMost(3)).bla(); // max 3 calls
 

使用ArgumentCaptor驗證參數

要驗證在mock上調用的方法的ArgumentCaptor ,請使用ArgumentCaptor 類。這將允許您將參數提取到測試方法中並對它們執行斷言。

此示例測試一個更新具有給定ID的用戶名的方法。該方法加載用戶,使用給定值更新name 屬性並在之後保存。測試想要驗證傳遞給save 方法的參數是具有正確ID和名稱的User 對象。

// This is mocked in the test
interface UserDao {
    void save(User user);
}

@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {
    @Mock
    UserDao userDao;

    @Test
    public void testSetNameForUser() {
        UserService serviceUnderTest = new UserService(userDao);
        
        serviceUnderTest.setNameForUser(1L, "John");

        ArgumentCaptor<User> userArgumentCaptor = ArgumentCaptor.forClass(User.class);
        
        verify(userDao).save(userArgumentCaptor.capture());
        User savedUser = userArgumentCaptor.getValue();
        assertTrue(savedUser.getId() == 1);
        assertTrue(savedUser.getName().equals("John"));
    }
}
 

使用ArgumentMatcher驗證參數

Mockito提供了一個Matcher<T> 接口以及一個抽象的ArgumentMatcher<T> 類來驗證參數。它使用與ArgumentCaptor 相同的用例的不同方法。此外,ArgumentMatcher也可用於模擬。兩個用例都使用Mockito.argThat() 方法,該方法提供了一個合理可讀的測試代碼。

verify(someMock).someMethod(Mockito.argThat(new ArgumentMatcher<String>() {
       
    @Override
    public boolean matches(Object o) {
        return o instanceof String && !((String)o).isEmpty();
    }

});        
 

從ArgumentMatcher的JavaDocs:

警告:使用複雜的參數匹配是合理的,尤其是自定義參數匹配器,因為它可以使測試的可讀性降低。有時最好為傳遞給模擬的參數實現equals()(Mockito自然地使用equals()進行參數匹配)。這可以使測試更清潔。