注:
若是你还不了解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。
获取最新文章或想加入安卓单元测试交流群,请关注下方公众号