注:
若是你還不瞭解Mock的概念或Mockito框架的使用,請先看這篇文章。html
若是你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
來讓Mockito自動使用mock出來的mockUserManager
和mockValidator
構造出一個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);
是時候,看到這個被@InjectMocks
的LoginPresenter
有一個構造方法: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
是比較tricky的一個東西。好比說,若是LoginPresenter
的構造方法是空的,就是沒有參數:
public class LoginPresenter { private UserManager mUserManager; private PasswordValidator mPasswordValidator; public LoginPresenter() { } }
那麼Mockito依然會將LoginPresenter
裏面的mUserManager
和mPasswordValidator
初始化爲LoginPresenterTest
裏面的兩個mock對象,mockUserManager
和mockValidator
。這個效果跟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
。
在介紹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。
獲取最新文章或想加入安卓單元測試交流羣,請關注下方公衆號