原文链接
如果你想要在你的单测中尝试Mockito,这篇文章可以告诉你如何注入Mock的对象,如何Mock方法,包括返回类型为void的方法。
我们平时接触的大多数类都需要依赖其他的类。很多时候,类中的方法都需要委托其他类中的方法来处理一些事情。如果我们只用Junit对特定类进行单元测试,那我们的测试方法也需要依赖这些方法。但是,我们希望我们在做单元测试时可以摆脱对其他类的依赖。
CustomerService
中的addCustomer
方法。但是,在addCustomer
方法中调用了CustomerDao
类中的save()
方法。出于以下几点,我们不希望CustomerDao.save()
的方法被调用:
addCustomer()
方法中的逻辑save()
方法save()
的方法存在一些错误,我们也不希望我们的单测失败如果,你对JUnit的单测一无所知的话,可以参考作者更早的一篇文章How to write greate unit tests with JUnit
Mocktio is a mocking framework that tastes really good. It lets you write beautiful tests with a clean & simple API. Mockito doesn’t give you hangover because the tests are very readable and they produce clean verification errors.
-[“Mockito.” Mockito Framework Site. N.p., n.d. Web. 28 Apr. 2017.](https://site.mockito.org/)
下面让我们通过一个例子,看看我们怎么用Mocktio来去除单测中的依赖。在进行单元测试时,我们可以通过注入一个Mock的类来代替真正的实现。
public class CustomerService {
@Inject
private CustomerDao customerDao;
public boolean addCustomer(Customer customer){
if(customerDao.exists(customer.getPhone())){
return false;
}
return customerDao.save(customer);
}
public CustomerDao getCustomerDao() {
return customerDao;
}
public void setCustomerDao(CustomerDao customerDao) {
this.customerDao = customerDao;
}
}
下面使用Mockito来mockCustomerService
中的依赖
public class CustomerServiceTest {
@Mock
private CustomerDao daoMock;
@InjectMocks
private CustomerService service;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
@Test
public void test() {
//assertion here
}
}
让我么来看看例子中各个注解的作用:
@Mock
会为CustomerDao
创建一个mock的实现@InjectMocks
注解的字段在实例化的时候,会将@Mock
注解的对象注入到自己的实例中。setUp()
函数中的MockitoAnnotations.initMocks(this);
这一行被调用时,被创建的。setUp()
进行这些实例的初始化到目前为止,一切都很顺利。接下去我们需要告诉Mock的对象,当特定的方法被调用的时候,它们该怎么做。
when then
模式:
when(dao.save(customer)).thenReturn(true);
这一行代码会告诉Mockito框架,我们希望在这个mock的dao对象执行save()
方法时,只需要我们传入对应的这个customer的实例,它就会返回true.when
是Mockito类的一个静态方法,它会返回一个OngoingStubbing
(T
是Mock的对象被调用的方法的返回类型,在这个例子中这个T
是一个布尔值)OngoingStubbing stub = when(dao.save(customer));
OngoingStubbing
对象提供了以下几个非常有用的方法:
thenReturn(returnValue)
thenThrow(exception)
thenCallRealMethod()
thenAnswer()
- 这个方法可以用来设置一个更聪明的stub,也可以用来mock一些void的方法(参考see How to mock void method behavior)when(dao.save(customer)).thenReturn(true);
这里传进去的是一个实际存在的特定对象,有更好的写法吗?当然!我们可以用一个macher来替代实际的对象when(dao.save(any(Customer.class))).thenReturn(true);
Mockito.when(mapper.map(any(), "test")).thenReturn(new Something());
matchers can't be mixed with actual values in the list of arguments to a single method.
让我们再来看看Mockito.when
的用法:
package com.tdd;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.*;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
public class CustomerServiceTest {
@Mock
private CustomerDao daoMock;
@InjectMocks
private CustomerService service;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
@Test
public void testAddCustomer_returnsNewCustomer() {
when(daoMock.save(any(Customer.class))).thenReturn(new Customer());
Customer customer = new Customer();
assertThat(service.addCustomer(customer), is(notNullValue()));
}
//Using Answer to set an id to the customer which is passed in as a parameter to the mock method.
@Test
public void testAddCustomer_returnsNewCustomerWithId() {
when(daoMock.save(any(Customer.class))).thenAnswer(new Answer() {
@Override
public Customer answer(InvocationOnMock invocation) throws Throwable {
Object[] arguments = invocation.getArguments();
if (arguments != null && arguments.length > 0 && arguments[0] != null){
Customer customer = (Customer) arguments[0];
customer.setId(1);
return customer;
}
return null;
}
});
Customer customer = new Customer();
assertThat(service.addCustomer(customer), is(notNullValue()));
}
//Throwing an exception from the mocked method
@Test(expected = RuntimeException.class)
public void testAddCustomer_throwsException() {
when(daoMock.save(any(Customer.class))).thenThrow(RuntimeException.class);
Customer customer = new Customer();
service.addCustomer(customer);//
}
}
doAnswer
: 如果我们希望 mock的void方法去做一些事情doThrow
:如果你想要mock的方法被调用时抛出一个异常,那么你可以用Mockito.doThrow()
下面会展示一个例子,这个例子并不是很好的示例。我只是想通过这个示例,让你向你展示基本用法。
@Test
public void testUpdate() {
doAnswer(new Answer() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
Object[] arguments = invocation.getArguments();
if (arguments != null && arguments.length > 1 && arguments[0] != null && arguments[1] != null) {
Customer customer = (Customer) arguments[0];
String email = (String) arguments[1];
customer.setEmail(email);
}
return null;
}
}).when(daoMock).updateEmail(any(Customer.class), any(String.class));
// calling the method under test
Customer customer = service.changeEmail("[email protected]", "[email protected]");
//some asserts
assertThat(customer, is(notNullValue()));
assertThat(customer.getEmail(), is(equalTo("[email protected]")));
}
@Test(expected = RuntimeException.class)
public void testUpdate_throwsException() {
doThrow(RuntimeException.class).when(daoMock).updateEmail(any(Customer.class), any(String.class));
// calling the method under test
Customer customer = service.changeEmail("[email protected]", "[email protected]");
}
}
有返回值的方法可以通过对返回值进行断言来测试,但是一些返回类型为void的方法我们该怎么测试呢?我们测试的这个void方法可能会去调用其它的方法来完成它的工作,也可能只是处理以下传入的参数,也可能是用来产生一些数据,当然也可能是以上几种可能结合起来。不用担心,我们可以用Mockito来测试上面的所有场景。
Mocking又一个很棒的特性,再单元测试执行的过程中,我们可以mock对象中特定的方法被调用了几次。verify
的方法有两个:
VerificationMode
,Mockito中有几个方法可以提供一些常用的VerificationMode
。
times(int wantedNumberOfInvocations)
atLeast( int wantedNumberOfInvocations )
atMost( int wantedNumberOfInvocations )
calls( int wantedNumberOfInvocations )
only( int wantedNumberOfInvocations )
atLeastOnce()
never()
下面展示一个Mockito.verify
的例子
package com.tdd;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.*;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
public class CustomerServiceTest {
@Mock
private CustomerDao daoMock;
@InjectMocks
private CustomerService service;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
@Test
public void test() {
when(daoMock.save(any(Customer.class))).thenReturn(true);
Customer customer=new Customer();
assertThat(service.addCustomer(customer), is(true));
//verify that the save method has been invoked
verify(daoMock).save(any(Customer.class));
//the above is similar to : verify(daoMock, times(1)).save(any(Customer.class));
//verify that the exists method is invoked one time
verify(daoMock, times(1)).exists(anyString());
//verify that the delete method has never been invoked
verify(daoMock, never()).delete(any(Customer.class));
}
}
Mockito的另外一个非常棒的特性就是ArgumentCaptor
,通过ArgumentCaptor
我们可以捕获到传入到Mock对象或者是spied的方法中的参数。不多说,看例子:
package com.service;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.verify;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import com.dao.CustomerDao;
import com.entity.Customer;
public class CustomerServiceTest {
@Mock
private CustomerDao doaMock;
@InjectMocks
private CustomerService service;
@Captor
private ArgumentCaptor customerArgument;
public CustomerServiceTest() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testRegister() {
//Requirement: we want to register a new customer. Every new customer should be assigned a random token before saving in the database.
service.register(new Customer());
//captures the argument which was passed in to save method.
verify(doaMock).save(customerArgument.capture());
//make sure a token is assigned by the register method before saving.
assertThat(customerArgument.getValue().getToken(), is(notNullValue()));
}
}
为什么用Spy?
@Spy
注解,Mockito会给这个对象创建一个代理。因此,我们既可以调用真实的方法,同时也能对它进行交互验证。下面,还是通过一个例子来看一下
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.*;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
public class CustomerServiceTestV2 {
@Spy
private CustomerDaoImpl daoSpy;
@InjectMocks
private CustomerService service;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
@Test
public void test() {
Customer customer = new Customer();
assertThat(service.addCustomer(customer), is(false));
verify(daoSpy).save(any(Customer.class));
verify(daoSpy, times(1)).exists(anyString());
verify(daoSpy, never()).delete(any(Customer.class));
}
}
本文中的所有例子的源码都可以在这里找到
本文到这里就结束了,如果你想了解更多的Mockito的指南,可以看一下下面的几个链接: