安卓單元測試(九):使用Mockito Annotation快速建立Mock

注:
若是你還不瞭解Mock的概念或Mockito框架的使用,請先看這篇文章html

@Mock的基本用法

若是你follow了這個安卓單元測試系列文章,那麼到如今爲止,你應該很清楚mock的概念和使用了,建立Mock的方法咱們都知道:java

YourClass yourInstance = Mockito.mock(YourClass.class);

好比:android

public class LoginPresenterTest {

    @Test
    public void testLogin() {
        UserManager mockUserManager = mock(UserManager.class);
        PasswordValidator mockValidator = mock(PasswordValidator.class);
        Mockito.when(mockValidator.verifyPassword("xiaochuang is handsome")).thenReturn(true);

        LoginPresenter presenter = new LoginPresenter(mockUserManager, mockValidator);

        presenter.login("xiaochuang", "xiaochuang is handsome");

        verify(mockUserManager).performLogin("xiaochuang", "xiaochuang is handsome");
    }
}

雖然很簡單,可是若是一個測試類裏面不少測試方法都要用到mock,那寫起來就會有點麻煩,這時候咱們能夠寫一個@Before方法來做這個setup工做:git

public class LoginPresenterTest {

    UserManager mockUserManager;
    PasswordValidator mockValidator;
    LoginPresenter loginPresenter;

    @Before
    public void setup() {
        mockUserManager = mock(UserManager.class);
        mockValidator = mock(PasswordValidator.class);
        loginPresenter = new LoginPresenter(mockUserManager, mockValidator);
    }

    @Test
    public void testLogin() {
        Mockito.when(mockValidator.verifyPassword("xiaochuang is handsome")).thenReturn(true);
        loginPresenter.login("xiaochuang", "xiaochuang is handsome");

        verify(mockUserManager).performLogin("xiaochuang", "xiaochuang is handsome");
    }
}

這樣能夠部分上減小Mock的建立,然而Mock寫多了,你也會以爲有點煩,由於徹底是Boilerplate code。這裏有個更簡便的方法,那就是結合Mockito的Annotation和JUnit Rule,達到如下的效果:github

public class LoginPresenterTest {

    @Rule
    public MockitoRule mockitoRule = MockitoJUnit.rule();

    @Mock
    UserManager mockUserManager;

    @Mock
    PasswordValidator mockValidator;

    LoginPresenter loginPresenter;

    @Before
    public void setup() {
        loginPresenter = new LoginPresenter(mockUserManager, mockValidator);
    }

    @Test
    public void testLogin() {
        Mockito.when(mockValidator.verifyPassword("xiaochuang is handsome")).thenReturn(true);
        loginPresenter.login("xiaochuang", "xiaochuang is handsome");

        verify(mockUserManager).performLogin("xiaochuang", "xiaochuang is handsome");
    }
}

也就是說,在測試方法運行以前,自動把用@Mock標註過的field實例化成Mock對象,這樣在測試方法裏面就能夠直接用了,而不用手動經過Mockito.mock(YourClass.class)的方法來建立。這個實現化的過程是在MockitoRule這個Rule裏面進行的。咱們知道(不知道?)一個JUnit Rule會在每一個測試方法運行以前執行一些代碼,而這個Rule實現的效果就是在每一個測試方法運行以前將這個類的用了@Mock修飾過的field初始化成mock對象。若是你去看這個Rule的源代碼的話,其實重點就在一行代碼:框架

MockitoAnnotations.initMocks(target);

上面的target就是咱們的測試類(LoginPresenterTest)。因此,在上面的例子中,若是你不使用這個Rule的話,你能夠在@Before裏面加一行代碼:wordpress

@Before
public void setup() {
    MockitoAnnotations.initMocks(this);
    loginPresenter = new LoginPresenter(mockUserManager, mockValidator);
}

也能達到同樣的效果。單元測試

使用@InjectMocks

其實在上面的代碼中,咱們還能夠進一步的簡化。咱們可使用@InjectMocks來讓Mockito自動使用mock出來的mockUserManagermockValidator構造出一個LoginPresenter測試

public class LoginPresenterTest {

    @Rule
    public MockitoRule mockitoRule = MockitoJUnit.rule();

    @Mock
    UserManager mockUserManager;
    @Mock
    PasswordValidator mockValidator;

    @InjectMocks
    LoginPresenter loginPresenter;

    @Test
    public void testLogin() {
        Mockito.when(mockValidator.verifyPassword("xiaochuang is handsome")).thenReturn(true);
        loginPresenter.login("xiaochuang", "xiaochuang is handsome");

        verify(mockUserManager).performLogin("xiaochuang", "xiaochuang is handsome");
    }
}

這是由於Mockito在MockitoAnnotations.initMocks(this);是時候,看到這個被@InjectMocksLoginPresenter有一個構造方法:this

public LoginPresenter(UserManager userManager, PasswordValidator passwordValidator) {
        this.mUserManager = userManager;
        this.mPasswordValidator = passwordValidator;
    }

這個構造方法所須要的兩個參數正好這個測試類裏面有這樣的field被@Mock修飾過。因而就用這兩個field來做爲LoginPresenter的構造參數,將LoginPresenter構造出來了。這個叫作Constructor Injection
field的聲明順序實際上是無所謂的,你徹底能夠把

@InjectMocks
    LoginPresenter loginPresenter;

放在兩個@Mock field前面。此外,若是你以爲每一個測試類裏面都要寫這個Rule有點麻煩,你能夠建一個Test基類,而後把這個Rule的定義放在基類裏面,每一個Test類繼承這個基類,這樣就不用每一個測試類本身寫這個Rule了。

詭異的@InjectMocks

其實,@InjectMocks是比較tricky的一個東西。好比說,若是LoginPresenter的構造方法是空的,就是沒有參數:

public class LoginPresenter {
    private UserManager mUserManager;
    private PasswordValidator mPasswordValidator;

    public LoginPresenter() {
    }
}

那麼Mockito依然會將LoginPresenter裏面的mUserManagermPasswordValidator初始化爲LoginPresenterTest裏面的兩個mock對象,mockUserManagermockValidator。這個效果跟LoginPresenter有兩個構造參數是同樣的。也就是說,若是被@InjectMocks修飾的field只有一個默認的構造方法,那麼在inject mocks的時候,Mockito會去找被@InjectMocks修飾的field的類(這裏是LoginPresenter)的field,而後根據類型對應的初始化爲測試類裏面用@Mock修飾過的field,這個叫Field Injection。然而若是LoginPresenter只有一個UserManager的構造方法:

public class LoginPresenter {
    private UserManager mUserManager;
    private PasswordValidator mPasswordValidator;

    public LoginPresenter(UserManager userManager) {
        this.mUserManager = userManager;
    }
}

那麼,在上面的LoginPresenterTest例子裏面,只有mUserManager會被初始化爲mockUserManager, 而mPasswordValidator是不會初始化爲mockValidator的。這就是tricky的地方。
此外還有Property Setter Injection,也就是經過setter方法,自動的初始化@InjectMocks對象。這個要搞清楚具體的工做流程仍是有點小複雜的。這三種injection的優先級順序分別爲:
Constructor Injection > Property Setter Injection > Field Injection。 具體狀況能夠在這裏看到。不少人其實都不推薦使用@InjectMocks,由於很難弄清楚到底會經過哪一種方式inject,我我的對這點也感受有點彆扭,我寧願經過new LoginPresenter(mockUserMananger, mockValiator)這種方式來建立LoginPresenter對象,而不是使用@InjectMocks

使用@Spy建立Spy對象

介紹Mock的時候,咱們還介紹了Spy,一樣的,咱們可使用@Spy來快速建立spy對象。

@Spy PasswordValidator spyValidator;
//or
@Spy PasswordValidator spyValidator = new PasswordValidator();

固然,就跟使用Mockito.spy()方法建立Spy對象同樣,要麼用@Spy修飾的field的類有默認的Constructor,要麼是一個對象。若是PasswordValidator沒有默認無參的構造方法,那麼@Spy PasswordValidator spyValidator;這個方式是會報錯的。

小結

本覺得這篇文章應該會很短,沒想到寫出來也不短了,這就跟作一個新feature同樣,乍一看好像很簡單,一個小時搞定,結果兩個小時之後,呃。。。

照例文中的代碼在github的這個repo

獲取最新文章或想加入安卓單元測試交流羣,請關注下方公衆號

相關文章
相關標籤/搜索