Mockito模拟指南

我们遇到的大多数类都具有依赖关系,并且通常情况下,方法将一些工作委派给其他类中的其他方法。这些类是我们的依赖项。在对这些方法进行单元测试时,如果仅使用JUnit,我们的测试也将依赖于这些方法。我们希望单元测试独立于所有其他依赖项。
假设我们要测试的方法 addCustomer 在 CustomerService 类中,这中 addCustomer 方法的的保存方法 CustomerDao 被调用的类。由于CustomerDao save() 某些原因,我们不想调用该方法的实际实现 :
o我们只想测试addCustomer() 隔离内部的逻辑 。
o我们可能尚未实施。
oaddCustomer() 如果中的save() 方法 存在缺陷,我们不希望单元测试 失败 CustomerDao。
因此,我们应该以某种方式模拟依赖项的行为。这就是模拟框架起作用的地方。
我只是用Mockito来做这个,在这篇文章中,我们将看到如何有效地使用Mockito模拟那些依赖关系。
如果您不熟悉使用JUnit进行单元测试,请查阅我的早期文章“ 如何使用JUnit编写出色的单元测试”。
什么是Mockito?
Mockito是一个非常不错的模拟框架。它使您可以使用干净简单的API编写漂亮的测试。Mockito不会给您带来麻烦,因为测试的可读性很强,并且会产生清晰的验证错误。
— “ Mockito。” Mockito框架站点。Np,网络。2017年4月28日。
用Mockito注射cks头
因此,回到上面的示例,我们如何使用Mockito模拟依赖关系?好吧,我们可以在运行测试时将模拟注入到要测试的类中,而不是真正的实现中!
让我们看一个受测试类的示例,它依赖于 CustomerDao:
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模拟依赖项的测试:
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
@[email protected] 创建实例时, 会将标有标记的模拟物注入 该实例。
那么 什么时候 或 在哪里 创建这些实例?好吧,这是在setUp方法中的这一行完成的:
MockitoAnnotations.initMocks(this);
因此,将在此测试类的每个测试方法的开始处创建这些实例。
Mockito的模拟方法
大!现在我们已经成功创建并注入了模拟,现在我们应该告诉模拟在调用某些方法时如何表现。
该 when then 模式:
我们在每种测试方法中都这样做。以下代码行告诉Mockito框架,我们希望save() 模拟DAO实例的 方法在传入特定客户实例时返回true。
when(dao.save(customer)).thenReturn(true);
when 是Mockito类的静态方法,它返回一个OngoingStubbing (T 是我们正在模拟 的方法的返回类型-在这种情况下为 boolean)。
因此,如果我们只是将其提取出来以获取存根,则它看起来像这样:
OngoingStubbing stub = when(dao.save(customer));
以下是我们可以调用的一些方法 stub
othenReturn(returnValue)
othenThrow(exception)
othenCallRealMethod()
othenAnswer() -这可以用于设置更智能的存根,也可以模拟void方法的行为(请参见如何模拟void方法的行为)。
只需将所有这些再次放在一行中: when(dao.save(customer)).thenReturn(true);
我们是否真的需要将实际的客户对象传递给此处的save方法?不,我们可以使用如下匹配器: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);//
}
}

用Mockito模拟无效方法
1.doAnswer:如果我们希望模拟的void方法做某事(尽管无效,也要模拟行为)。
2.doThrow:然后, Mockito.doThrow() 如果要从模拟的void方法中引发异常,就会出现这种情况。
以下是如何使用它的示例。请记住,这不是理想的用例,但我想说明一下基本用法:
@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]”);
}
}

用Mockito测试无效方法的两种方法
可以通过声明返回值来测试具有返回值的方法,但是我们如何测试void方法呢?您要测试的void方法可能是调用其他方法来完成工作,处理输入参数,或者可能生成某些值或全部。使用Mockito,您可以测试上述所有方案。
用Mockito验证
关于模拟的一个伟大之处在于,当被测方法无效时,我们可以验证在测试执行期间是否已在断言之外或代替断言而在那些模拟对象上调用了某些方法。
有两种重载的验证方法。
o仅接受模拟对象的对象-如果该方法仅应调用一次,则可以使用此对象。
o另一个接受模拟,一个接受 VerificationMode — Mockito类中有很多方法提供了一些有用的verifyModes:
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));
}
}

捕获参数
另一个很酷的功能是 ArgumentCaptor,它使我们能够捕获传递给模拟或间谍方法的任何参数。
Mockito.ArgumentCaptor:
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()));
}
}

我监视:监视Mockito
为什么要间谍?
有时我们需要调用依赖项的真实方法,但仍然想验证或跟踪与该依赖项的交互。这是我们使用间谍的地方。
当使用注释字段时 @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));
}
}

最后,开发这么多年我也总结了一套学习Java的资料与面试题,如果你在技术上面想提升自己的话,可以关注我,私信发送领取资料或者在评论区留下自己的联系方式,有时间记得帮我点下转发让跟多的人看到哦。Mockito模拟指南