简介
所谓mock就是创建一个类的虚假对象,在测试环境中,替换掉真实对象,以达到以下目的:
·验证这个对象的某个方法的调用情况,比如调用了多少次、参数是什么等等;
· 指定这个对象的某些方法的行为,比如返回特定的值、执行特定的动作等。
要使用Mock,一般需要用到mock框架,本文介绍Mockito这个框架,这个是Java界使用最广泛的一个mock框架。
比如测试用户登陆:
public class LoginPresenter {
private UserManager userManager = new UserManager();
public void login(String username, String password) {
if (username == null || username.length() == 0) return;
if (password == null || password.length() < 6) return;
userManager.performLogin(username, password);
}
public void setUserManager(UserManager userManager) {
this.userManager = userManager;
}
}
对应的测试类:
public class LoginPresenterTest {
LoginPresenter loginPresenter;
@Before
public void setUp() throws Exception {
loginPresenter = new LoginPresenter();
}
@Test
public void testLogin() {
UserManager mockUserManager = Mockito.mock(UserManager.class);
//loginPresenter中使用的UserManager必须是Mock出来的对象,只有这样才能验证UserManager.performLogin,
// 所以需要将mock出来的对象传给loginPresenter
loginPresenter.setUserManager(mockUserManager);
loginPresenter.login("aya", "123456");
//verify传入的必须mock出来的对象,而且必须与loginPresenter中使用的UserManager对象是同一个
Mockito.verify(mockUserManager).performLogin("aya", "123456");
}
}
注意
Mockito.mock()并不是mock一整个类,而是根据传进去的一个类,mock出属于这个类的一个对象,并且返回这个mock对象;而传进去的这个类本身并没有改变,用这个类new出来的对象也没有受到任何改变!
比如这样写就会出错:
public class LoginPresenterTest {
@Test
public void testLogin() {
Mockito.mock(UserManager.class);
UserManager userManager = loginPresenter.getUserManager();
loginPresenter.login("aya", "123456");
Mockito.verify(userManager).performLogin("aya", "123456");
}
}
mock出来的对象并不会自动替换掉正式代码里面的对象,你必须要有某种方式把mock对象应用到正式代码里面,上面例子中使用setUserManager()传入了mock的对象。所以下面这个也是个错误的例子:
public class LoginPresenterTest {
@Test
public void testLogin(){
UserManager mockUserManager = Mockito.mock(UserManager.class);
LoginPresenter loginPresenter = new LoginPresenter();
loginPresenter.login("aya", "123456");
Mockito.verify(mockUserManager).performLogin("aya", "123456");
}
}
使用setter的方式传入mock对象时,如果该setter方法其他地方没用到,只是为了测试而增加的,不是很优雅。可以使用依赖注入的方式来解决,比如把UserManager作为LoginPresenter的构造函数的参数,最终如下:
public class LoginPresenter {
private UserManager mUserManager;
public LoginPresenter(UserManager userManager){
mUserManager = userManager;
}
public void login(String username, String password) {
if (username == null || username.length() == 0) return;
if (password == null || password.length() < 6) return;
mUserManager.performLogin(username, password);
}
}
public class LoginPresenterTest {
LoginPresenter loginPresenter;
@Test
public void testLogin() {
UserManager mockUserManager = Mockito.mock(UserManager.class);
//loginPresenter中使用的UserManager必须是Mock出来的对象,只有这样才能验证UserManager.performLogin,
// 所以需要将mock出来的对象传给loginPresenter
loginPresenter = new LoginPresenter(mockUserManager);
loginPresenter.login("aya", "123456");
//verify传入的必须mock出来的对象,而且必须与loginPresenter中使用的UserManager对象是同一个
Mockito.verify(mockUserManager).performLogin("aya", "123456");
}
}
验证方法调用
查看Mockito源码,可以看到verify()还有一个重载的方法:
/**
* Verifies certain behavior happened at least once / exact number of times / never. E.g:
* <pre class="code"><code class="java">
* verify(mock, times(5)).someMethod("was called five times");
*
* verify(mock, atLeast(2)).someMethod("was called at least two times");
*
* //you can use flexible argument matchers, e.g:
* verify(mock, atLeastOnce()).someMethod(<b>anyString()</b>);
* </code></pre>
*
* <b>times(1) is the default</b> and can be omitted
* <p>
* Arguments passed are compared using <code>equals()</code> method.
* Read about {@link ArgumentCaptor} or {@link ArgumentMatcher} to find out other ways of matching / asserting arguments passed.
* <p>
*
* @param mock to be verified
* @param mode times(x), atLeastOnce() or never()
*
* @return mock object itself
*/
@CheckReturnValue
public static <T> T verify(T mock, VerificationMode mode) {
return MOCKITO_CORE.verify(mock, mode);
}
其中VerificationMode源码如下:
/**
* Allows verifying that certain behavior happened at least once / exact number
* of times / never. E.g:
*
* <pre class="code"><code class="java">
* verify(mock, times(5)).someMethod("was called five times");
*
* verify(mock, never()).someMethod("was never called");
*
* verify(mock, atLeastOnce()).someMethod("was called at least once");
*
* verify(mock, atLeast(2)).someMethod("was called at least twice");
*
* verify(mock, atMost(3)).someMethod("was called at most 3 times");
*
* </code></pre>
*
* <b>times(1) is the default</b> and can be omitted
* <p>
* See examples in javadoc for {@link Mockito#verify(Object, VerificationMode)}
*/
public interface VerificationMode {
/**
* Performs the verification
*/
void verify(VerificationData data);
/**
* Description will be prepended to the assertion error if verification fails.
* @param description The custom failure message
* @return VerificationMode
* @since 2.1.0
*/
VerificationMode description(String description);
}
可以看出,在验证方法调用时,可以使用times()指定调用次数,使用never()验证从未调用,使用 atLeastOnce()验证至少调用了一次,使用atLeast()验证至少调用次数,使用atMost()验证最多调用次数。
如果你不关心传入的参数,只关心方法是否调用,Mockito也提供了一系列的any方法,来表示任何的参数都行:
Mockito.verify(mockUserManager).performLogin(Mockito.anyString(), Mockito.anyString());
Mockito.anyString()表示任意非null字符串,类似的有anyFloat(),anyBoolean,anyByte(),anyLong(),也提供了anyList,anyListOf(Class<T> clazz),anySet(),anyMap(),anyCollection等方法,甚至isNotNull(),isNotNull(Class<T> clazz),anyObject,更多方法去看源码。
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理