徐州模板建站定制网站,wordpress给栏目页加后缀,做教育集团的网站,龙华做网站多少钱本文是我们名为“ 用Mockito进行测试 ”的学院课程的一部分。 在本课程中#xff0c;您将深入了解Mockito的魔力。 您将了解有关“模拟”#xff0c;“间谍”和“部分模拟”的信息#xff0c;以及它们相应的Stubbing行为。 您还将看到使用测试双打和对象匹配器进行验证的过… 本文是我们名为“ 用Mockito进行测试 ”的学院课程的一部分。 在本课程中您将深入了解Mockito的魔力。 您将了解有关“模拟”“间谍”和“部分模拟”的信息以及它们相应的Stubbing行为。 您还将看到使用测试双打和对象匹配器进行验证的过程。 最后讨论了使用Mockito的测试驱动开发TDD以了解该库如何适合TDD的概念。 在这里查看 目录 1.简介 2.模拟存根间谍–名称是什么 3.存根方法 4.存根返回值 4.1。 使用答案 4.2。 有关行为驱动开发测试约定的说明 4.3。 在Eclipse中使用Mockito静态方法的提示 4.4。 使用多个模拟 4.5。 测试自己 测试更新 5.参数匹配器 6.间谍和部分存根 7.结论 8.下载源代码 1.简介 在本教程中我们将深入研究使用Mockito存根类和接口。 2.模拟存根间谍–名称是什么 嘲笑中的许多术语可以互换使用也可以作为动词和名词使用。 我们现在将对这些术语进行定义以避免将来造成混淆。 模拟名词 –一个对象充当另一个对象的双精度对象。 模拟动词 –创建模拟对象或对方法进行存根。 间谍名词 –装饰现有对象并允许对该对象的方法进行存根和对该对象的调用进行验证的对象。 间谍动词 –创建和使用间谍对象。 存根名词 –可以在调用方法时提供“罐头答案”的对象。 存根动词 –创建固定答案。 Partial MockPartial Stub动词 –间谍的另一个术语其中包括某些方法的代码。 从技术上讲Mockito是一个测试间谍框架而不是模拟框架因为它允许我们创建间谍和验证行为以及创建具有残存行为的模拟对象。 正如在上一教程中所看到的我们可以使用when().thenReturn()方法对给定接口或类的行为进行存根。 现在我们将研究为Mocks和Spies提供存根的所有方式。 3.存根方法 给定以下界面 public interface Printer {void printTestPage();} 以下是基于它的基于字符串缓冲区的简单化“字处理器”类 public class StringProcessor {private Printer printer;private String currentBuffer;public StringProcessor(Printer printer) {this.printer printer;}public OptionalString statusAndTest() {printer.printTestPage();return Optional.ofNullable(currentBuffer);}} 我们要编写一个测试方法该方法将在构造后测试当前缓冲区是否不存在并处理测试页的打印。 这是我们的测试班 public class StringProcessorTest {private Printer printer;Testpublic void internal_buffer_should_be_absent_after_construction() {// GivenStringProcessor processor new StringProcessor(printer);// WhenOptionalString actualBuffer processor.statusAndTest();// ThenassertFalse(actualBuffer.isPresent());}
} 我们知道statusAndTest()将涉及对Printer的printTestPage()方法的调用并且printer引用未初始化因此如果执行此测试我们将以NullPointerException结尾。 为了避免这种情况我们只需要注释测试类以告诉JUnit使用Mockito运行它并注释Printer作为一个模拟以告诉mockito为此创建一个模拟。 RunWith(MockitoJUnitRunner.class)
public class StringProcessorTest {Mockprivate Printer printer;Testpublic void internal_buffer_should_be_absent_after_construction() {// GivenStringProcessor processor new StringProcessor(printer);// WhenOptionalString actualBuffer processor.statusAndTest();// ThenassertFalse(actualBuffer.isPresent());}} 现在我们可以执行测试Mockito将为我们创建Printer的实现并将其实例分配给printer变量。 我们将不再获得NullPointerException。 但是如果Printer是一类实际完成某些工作的类例如打印物理测试页该怎么办 如果我们选择了Spy而不是创建Mock怎么办 记住除非被侦听否则间谍会在类上调用间谍的真实方法。 我们希望避免在调用该方法时做任何实际的事情。 让我们做一个简单的Printer实现 public class SysoutPrinter implements Printer {Overridepublic void printTestPage() {System.out.println(This is a test page);}} 并将其作为间谍添加到我们的测试类中并添加一个新方法来测试使用它 Spyprivate SysoutPrinter sysoutPrinter;Testpublic void internal_buffer_should_be_absent_after_construction_sysout() {// GivenStringProcessor processor new StringProcessor(sysoutPrinter);// WhenOptionalString actualBuffer processor.statusAndTest();// ThenassertFalse(actualBuffer.isPresent());} 如果现在执行此测试您将在控制台上看到以下输出 This is a test page 这证实了我们的测试用例实际上是在执行SysoutPrinter类的真实方法这是因为它是Spy而不是Mock。 如果该类实际执行了测试页的实际物理打印那将是非常不希望的 当我们执行部分模拟或Spy时可以使用org.mockito.Mockito.doNothing()调用的方法进行存根以确保其中没有任何org.mockito.Mockito.doNothing() 。 让我们添加以下导入和测试 import static org.mockito.Mockito.*;Testpublic void internal_buffer_should_be_absent_after_construction_sysout_with_donothing() {// GivenStringProcessor processor new StringProcessor(sysoutPrinter);doNothing().when(sysoutPrinter).printTestPage();// WhenOptionalString actualBuffer processor.statusAndTest();// ThenassertFalse(actualBuffer.isPresent());} 注意方法doNothing.when(sysoutPrinter).printTestPage() 这告诉Mockito当调用Spy sysoutPrinter的void方法printTestPage 不应执行真正的方法而应执行任何操作。 现在当我们执行此测试时屏幕上看不到任何输出。 如果未连接物理打印机如果我们扩展打印机接口以引发新的PrinterNotConnectedException异常该怎么办 我们如何测试这种情况 首先让我们创建一个非常简单的新异常类。 public class PrinterNotConnectedException extends Exception {private static final long serialVersionUID -6643301294924639178L;} 并修改我们的界面以将其抛出 void printTestPage() throws PrinterNotConnectedException; 如果抛出异常我们还需要修改StringProcessor以执行某些操作。 为了简单起见我们只将异常抛出给调用类。 public OptionalString statusAndTest() throws PrinterNotConnectedException 现在我们要测试异常是否传递给调用类因此我们必须强制打印机抛出异常。 与doNothing()类似我们可以使用doThrow强制执行异常。 让我们添加以下测试 Test(expected PrinterNotConnectedException.class)public void printer_not_connected_exception_should_be_thrown_up_the_stack() throws Exception {// GivenStringProcessor processor new StringProcessor(printer);doThrow(new PrinterNotConnectedException()).when(printer).printTestPage();// WhenOptionalString actualBuffer processor.statusAndTest();// ThenassertFalse(actualBuffer.isPresent());} 在这里我们看到可以使用doThrow()抛出所需的任何异常。 在这种情况下我们将抛出满足我们测试要求的PrinterNotConnectedException 。 现在我们已经学习了如何对void方法进行存根让我们看一下返回一些数据。 4.存根返回值 让我们开始创建一个数据访问对象以持久性地从数据库中检索客户对象。 该DAO将使用内部的企业java EntityManager接口进行实际的数据库交互。 为了使用EntityManager我们将使用JPA 2.0的Hibernate实现将以下依赖项添加到pom.xml中 dependencygroupIdorg.hibernate.javax.persistence/groupIdartifactIdhibernate-jpa-2.0-api/artifactIdversion1.0.1.Final/version/dependency 现在我们将创建一个简单的Customer实体来表示要保留的Customer。 Entity
public class Customer {Id GeneratedValueprivate long id;private String name;private String address;public Customer() {}public Customer(long id, String name, String address) {super();this.id id;this.name name;this.address address;}public long getId() {return id;}public void setId(long id) {this.id id;}public String getAddress() {return address;}public void setAddress(String address) {this.address address;}public String getName() {return name;}public void setName(String name) {this.name name;}} 现在我们将创建一个框架DAO该框架使用PersistenceContext配置注入的EntityManager 。 我们不必担心使用Java持久性体系结构JPA或它如何工作-我们将使用Mockito完全绕过它但这是Mockito实际应用的一个很好的示例。 public class CustomerDAO {PersistenceContextEntityManager em;public CustomerDAO(EntityManager em) {this.em em;}} 我们将在DAO中添加基本的“检索和更新”功能并使用Mockito对其进行测试。 首先使用Retrieve方法-我们将传递一个ID并从数据库中返回适当的Customer如果存在。 public OptionalCustomer findById(long id) throws Exception {return Optional.ofNullable(em.find(Customer.class, id));} 在这里我们使用Java Optional来避免对结果进行空检查。 现在我们可以添加测试以在找到客户但找不到客户的位置测试此方法–我们将使用Mockito方法org.mockito.Mockito.when存根find()方法以在每种情况下返回适当的Optional。然后thenReturn() 让我们如下创建Test类为Mockito方法import static org.mockito.Mockito.*; RunWith(MockitoJUnitRunner.class)
public class CustomerDAOTest {private CustomerDAO dao;Mockprivate EntityManager mockEntityManager;Beforepublic void setUp() throws Exception {dao new CustomerDAO(mockEntityManager);}Testpublic void finding_existing_customer_should_return_customer() throws Exception {// Givenlong expectedId 10;String expectedName John Doe;String expectedAddress 21 Main Street;Customer expectedCustomer new Customer(expectedId, expectedName, expectedAddress);when(mockEntityManager.find(Customer.class, expectedId)).thenReturn(expectedCustomer);// WhenOptionalCustomer actualCustomer dao.findById(expectedId);// ThenassertTrue(actualCustomer.isPresent());assertEquals(expectedId, actualCustomer.get().getId());assertEquals(expectedName, actualCustomer.get().getName());assertEquals(expectedAddress, actualCustomer.get().getAddress());}
} 我们看到了用于启用模仿 EntityManger并将其注入到测试中的类的常用样板。 让我们看一下测试方法。 第一行涉及创建具有已知期望值的Customer 然后我们看到对Mockito的调用告诉我们当使用我们提供的特定输入参数调用EntityManager.find()方法时该客户将返回此客户。 然后我们执行findById()方法和一组断言的实际执行以确保获得所需的值。 让我们剖析Mockito调用 when(mockEntityManager.find(Customer.class, expectedId)).thenReturn(expectedCustomer); 这演示了Mockito强大而优雅的语法。 读起来几乎像普通的英语。 当find()的方法mockEntityManager对象被称为与特定输入Customer.class和expectedId 然后返回expectedCustomer对象。 如果您使用未告知其期望的参数调用Mock则它将仅返回null如以下测试所示 Testpublic void invoking_mock_with_unexpected_argument_returns_null() throws Exception {// Givenlong expectedId 10L;long unexpectedId 20L;String expectedName John Doe;String expectedAddress 21 Main Street;Customer expectedCustomer new Customer(expectedId, expectedName, expectedAddress);when(mockEntityManager.find(Customer.class, expectedId)).thenReturn(expectedCustomer);// WhenOptionalCustomer actualCustomer dao.findById(unexpectedId);// ThenassertFalse(actualCustomer.isPresent());} 您还可以将Mock存根几次以实现不同的行为具体取决于输入。 让我们让Mock根据输入的ID返回其他客户 Testpublic void invoking_mock_with_different_argument_returns_different_customers() throws Exception {// Givenlong expectedId1 10L;String expectedName1 John Doe;String expectedAddress1 21 Main Street;Customer expectedCustomer1 new Customer(expectedId1, expectedName1, expectedAddress1);long expectedId2 20L;String expectedName2 Jane Deer;String expectedAddress2 46 High Street;Customer expectedCustomer2 new Customer(expectedId2, expectedName2, expectedAddress2);when(mockEntityManager.find(Customer.class, expectedId1)).thenReturn(expectedCustomer1);when(mockEntityManager.find(Customer.class, expectedId2)).thenReturn(expectedCustomer2);// WhenOptionalCustomer actualCustomer1 dao.findById(expectedId1);OptionalCustomer actualCustomer2 dao.findById(expectedId2);// ThenassertEquals(expectedName1, actualCustomer1.get().getName());assertEquals(expectedName2, actualCustomer2.get().getName());} 您甚至可以链接返回以使模拟在每次调用时执行不同的操作。 请注意如果您调用模拟程序的次数超过了您的存根行为那么它将永远永远根据最后一个存根行为。 Testpublic void invoking_mock_with_chained_stubs_returns_different_customers() throws Exception {// Givenlong expectedId1 10L;String expectedName1 John Doe;String expectedAddress1 21 Main Street;Customer expectedCustomer1 new Customer(expectedId1, expectedName1, expectedAddress1);long expectedId2 20L;String expectedName2 Jane Deer;String expectedAddress2 46 High Street;Customer expectedCustomer2 new Customer(expectedId2, expectedName2, expectedAddress2);when(mockEntityManager.find(Customer.class, expectedId1)).thenReturn(expectedCustomer1).thenReturn(expectedCustomer2);// WhenOptionalCustomer actualCustomer1 dao.findById(expectedId1);OptionalCustomer actualCustomer2 dao.findById(expectedId1);// ThenassertEquals(expectedName1, actualCustomer1.get().getName());assertEquals(expectedName2, actualCustomer2.get().getName());} 请注意我们输入了相同的ID到两个电话不同的行为是由第二goverened theReturn()方法这只能是因为when()存根的一部分明确预期和输入expectedId1 如果我们通过expectedId2我们由于它不是存根中的期望值因此可能会从模拟中获得空响应。 现在让我们测试客户丢失的情况。 Testpublic void finding_missing_customer_should_return_null() throws Exception {// Givenlong expectedId 10L;when(mockEntityManager.find(Customer.class, expectedId)).thenReturn(null);// WhenOptionalCustomer actualCustomer dao.findById(expectedId);// ThenassertFalse(actualCustomer.isPresent());} 在这里我们可以看到我们使用相同的语法但是这次使用它来返回null。 允许的Mockito您使用的可变参数thenReturn存根连续调用所以如果我们想我们可以在前面的两个测试擀成一个如下 Testpublic void finding_customer_should_respond_appropriately() throws Exception {// Givenlong expectedId 10L;String expectedName John Doe;String expectedAddress 21 Main Street;Customer expectedCustomer1 new Customer(expectedId, expectedName, expectedAddress);Customer expectedCustomer2 null;when(mockEntityManager.find(Customer.class, expectedId)).thenReturn(expectedCustomer1, expectedCustomer2);// WhenOptionalCustomer actualCustomer1 dao.findById(expectedId);OptionalCustomer actualCustomer2 dao.findById(expectedId);// ThenassertTrue(actualCustomer1.isPresent());assertFalse(actualCustomer2.isPresent());} 如果我们的find方法由于某些持久性问题而引发异常怎么办 让我们测试一下 Test(expectedIllegalArgumentException.class)public void finding_customer_should_throw_exception_up_the_stack() throws Exception {// Givenlong expectedId 10L;when(mockEntityManager.find(Customer.class, expectedId)).thenThrow(new IllegalArgumentException());// Whendao.findById(expectedId);// Thenfail(Exception should be thrown.);} 我们使用了thenThrow()方法引发异常。 在对无效方法进行存根时将此语法与我们对doThrow()使用进行doThrow() 。 这是两个相似但不同的方法– thenThrow()将不适用于void方法。 使用答案 我们在上面看到我们创建了具有某些期望值的客户。 如果我们想创建一些已知的测试用户并以id为基础返回他们则可以使用Answer 可以从when()调用中返回。 Answer是Mockito提供的通用类型用于提供“罐头响应”。 它的answer()方法采用一个InvocationOnMock对象该对象包含有关当前模拟方法调用的某些信息。 让我们创建3个客户和一个Answer根据输入的ID选择要返回的客户。 首先将3位客户添加为测试类的私有成员。 private Customer homerSimpson, bruceWayne, tyrionLannister; 然后添加一个专用的setupCustomers方法以对其进行初始化然后从Before方法进行调用。 Beforepublic void setUp() throws Exception {dao new CustomerDAO(mockEntityManager);setupCustomers();}private void setupCustomers() {homerSimpson new Customer(1, Homer Simpson, Springfield);bruceWayne new Customer(2, Bruce Wayne, Gotham City);tyrionLannister new Customer(2, Tyrion Lannister, Kings Landing);} 现在我们可以基于运行时传递给传递给模拟EntityManager的find()方法的ID创建一个Answer来返回适当的Customer。 private AnswerCustomer withCustomerById new AnswerCustomer() {Overridepublic Customer answer(InvocationOnMock invocation) throws Throwable {Object[] args invocation.getArguments();int id ((Long)args[1]).intValue(); // Cast to int for switch.switch (id) {case 1 : return homerSimpson;case 2 : return bruceWayne;case 3 : return tyrionLannister;default : return null;}}}; 我们可以看到我们使用InvocationOnMock提取了传递到Mock方法调用中的参数。 我们知道第二个参数是ID因此我们可以读取该参数并确定要返回的适当客户。 稍后带有withCustomerById的答案的名称将适合我们的模拟语法。 现在让我们编写一个测试来证明此答案的实际效果。 Testpublic void finding_customer_by_id_returns_appropriate_customer() throws Exception {// Givenlong[] expectedId {1, 2, 3};when(mockEntityManager.find(eq(Customer.class), anyLong())).thenAnswer(withCustomerById);// WhenOptionalCustomer actualCustomer0 dao.findById(expectedId[0]);OptionalCustomer actualCustomer1 dao.findById(expectedId[1]);OptionalCustomer actualCustomer2 dao.findById(expectedId[2]);// ThenassertEquals(Homer Simpson, actualCustomer0.get().getName());assertEquals(Bruce Wayne, actualCustomer1.get().getName());assertEquals(Tyrion Lannister, actualCustomer2.get().getName());} 让我们详细看一下存根线。 when(mockEntityManager.find(eq(Customer.class), anyLong())).thenAnswer(withCustomerById); 在这里我们看到了一些新事物。 第一件事是我们不执行when().thenReturn()而是执行when().thenAnswer()并提供withCustomerById Answer作为要给出的答案。 第二件事是我们不对传递给mockEntityManager.find()的ID使用真实值而是使用静态org.mockito.Matchers.anyLong() 。 这是一个Matcher 用于使Mockito发出Answer而无需检查是否已传递特定的Long值。Matchers让我们忽略模拟调用的参数而只专注于返回值。 我们还用eq() Matcher装饰了Customer.class –这是由于您不能在Mock方法调用中混合使用实值和Matchers您要么必须将所有参数都用作Matchers要么必须将所有参数都用作实值。 eq()提供了一个Matcher仅当运行时参数等于存根中的指定参数时才匹配。 让我们继续仅在输入类类型为Customer.class类型时不指定特定ID的情况下才返回Answer。 什么这一切意味着三个调用mockEntityManager.find()用不同的ID是所有产生相同的答案报错并且我们已经编码的答案与不同的ID相应的客户对象响应是我们已经成功地嘲笑一个EntityManager能力模仿现实行为。 有关行为驱动开发测试约定的说明 您可能已经注意到我们在单元测试中采用了约定将测试分为三部分– //给定//时间和//然后。 该约定称为行为驱动开发是设计单元测试的非常合乎逻辑的方法。 // 给定的是设置阶段在该阶段我们初始化数据和存根模拟类。 它与陈述“给定以下初始条件”相同。 //什么时候是执行阶段在该阶段我们执行被测方法并捕获所有返回的对象。 //然后是验证阶段在此阶段我们放置断言逻辑该逻辑将检查该方法是否表现出预期的行为。 Mockito在org.mockito.BDDMockito类中开箱即用地支持BDD。 它用BDD doppelgangers given() willReturn() willThrow() willAnswer() willAnswer()替换了常规的存根方法– when() thenReturn() thenThrow() thenAnswer()等。 这样可以避免在// //给定部分中使用when() 因为这可能会造成混淆。 因为我们在测试中使用BDD约定所以我们还将使用BDDMockito提供的方法。 让我们使用BDDMockito语法重写finding_existing_customer_should_return_customer() 。 import static org.mockito.BDDMockito.*;Testpublic void finding_existing_customer_should_return_customer_bdd() throws Exception {// Givenlong expectedId 10L;String expectedName John Doe;String expectedAddress 21 Main Street;Customer expectedCustomer new Customer(expectedId, expectedName, expectedAddress);given(mockEntityManager.find(Customer.class, expectedId)).willReturn(expectedCustomer);// WhenOptionalCustomer actualCustomer dao.findById(expectedId);// ThenassertTrue(actualCustomer.isPresent());assertEquals(expectedId, actualCustomer.get().getId());assertEquals(expectedName, actualCustomer.get().getName());assertEquals(expectedAddress, actualCustomer.get().getAddress());} 测试的逻辑没有改变只是以BDD格式可读。 在Eclipse中使用Mockito静态方法的提示 如果要避免导入org.mockito.Mockito.*等为各种Mockito静态方法手动添加静态导入可能会很痛苦org.mockito.Mockito.*为了在Eclipse中为这些方法启用内容辅助您只需要启动org.mockito.Mockito.* Preferences并转到左侧导航栏中的Java / Editor / Content Assist / Favorites。 然后按照图1添加以下内容作为“ New Type…”。 org.mockito.Mockito org.mockito.Matchers org.mockito.BDDMockito 这会将Mockito静态方法添加到Eclipse Content Assist中使您可以在使用它们时自动完成并导入它们。 图1 – Content Assist收藏夹 使用多个模拟 现在我们将结合在一起使用多个模拟。 让我们向DAO中添加一个方法以返回所有可用客户的列表。 public ListCustomer findAll() throws Exception {TypedQueryCustomer query em.createQuery(select * from CUSTOMER, Customer.class);return query.getResultList();} 在这里我们看到EntityManager的createQuery()方法返回一个通用类型TypedQuery 。 它接受一个SQL String和一个作为返回类型的类作为参数。 TypedQuery本身公开了几种方法包括List getResultList() 这些方法可用于执行返回多个值的查询例如上面的select * from CUSTOMER查询中的select * from CUSTOMER 。 为了对此方法编写测试我们将要创建一个TypedQuery的Mock。 Mock
private TypedQueryCustomer mockQuery; 现在我们可以对这个模拟查询进行存根以返回已知客户的列表。 让我们创建一个答案来做到这一点并重用我们先前创建的已知客户。 您可能已经注意到Answer是一个功能接口只有一种方法。 我们正在使用Java 8因此我们可以创建一个lambda表达式来内联地表示我们的Answer而不是像前面的Answer示例中那样创建一个匿名内部类。 given(mockQuery.getResultList()).willAnswer(i - Arrays.asList(homerSimpson, bruceWayne, tyrionLannister)); 当然我们也可以将上面的存根编码为 given(mockQuery.getResultList()).willReturn(Arrays.asList(homerSimpson, bruceWayne, tyrionLannister));given 这展示了Mockito的灵活性–总是有几种不同的方式来做相同的事情。 现在我们已经对模拟TypedQuery的行为进行了存根我们可以对模拟EntityManager进行存根以在请求时返回它。 与其将SQL引入我们的测试用例中 anyString()仅使用anyString()匹配器来触发模拟createQuery() 当然我们还将用eq()匹配器包围该类参数。 完整的测试如下所示 Testpublic void finding_all_customers_should_return_all_customers() throws Exception {// Givengiven(mockQuery.getResultList()).willAnswer(i - Arrays.asList(homerSimpson, bruceWayne, tyrionLannister));given(mockEntityManager.createQuery(anyString(), eq(Customer.class))).willReturn(mockQuery);// WhenListCustomer actualCustomers dao.findAll();// ThenassertEquals(actualCustomers.size(), 3);}测试更新 让我们添加Update() DAO方法 public Customer update(Customer customer) throws Exception {return em.merge(customer);} 现在查看是否可以为其创建测试。 本教程随附的示例代码项目中已编写了可能的解决方案。 记住在Mockito中有很多方法可以做相同的事情看看是否能想到其中的几种 5.参数匹配器 Mocktio的自然行为是使用对象的equals()方法作为参数传入以查看是否存在特定的存根行为。 但是如果对于我们来说不重要的是那些值那么在存根时可以避免使用实际的对象和变量。 我们通过使用Mockito参数匹配器来实现 我们已经看到了一些运行中的Mockito参数匹配器 anyLong() anyString()和eq 。 当我们不特别在意Mock的输入时我们会使用这些匹配器我们只对编码它的返回行为感兴趣并且我们希望它在所有条件下的行为都相同。 如前所述但需要特别注意的是当使用参数匹配器时所有参数都必须是参数匹配器您不能将实值与参数匹配器混合和匹配否则会从Mockito中获取运行时错误。 参数匹配器都扩展了org.mockito.ArgumentMatcher Mockito包括一个现成的参数匹配器库可以通过org.mockito.Matchers的静态方法进行org.mockito.Matchers 要使用它们只需导入org.mockito.Matchers.* ; 您可以查看org.mockito.Matchers的javadoc以查看Mockito提供的所有Matchers而以下测试类演示了其中一些用法 package com.javacodegeeks.hughwphamill.mockito.stubbing;import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;RunWith(MockitoJUnitRunner.class)
public class MatchersTest {public interface TestForMock {public boolean usesPrimitives(int i, float f, double d, byte b, boolean bool);public boolean usesObjects(String s, Object o, Integer i);public boolean usesCollections(ListString list, MapInteger, String map, SetObject set);public boolean usesString(String s);public boolean usesVarargs(String... s);public boolean usesObject(Object o);}MockTestForMock test;Testpublic void test() {// default behaviour is to return falseassertFalse(test.usesString(Hello));when(test.usesObjects(any(), any(), any())).thenReturn(true);assertTrue(test.usesObjects(Hello, new Thread(), 17));Mockito.reset(test);when(test.usesObjects(anyString(), anyObject(), anyInt())).thenReturn(true);assertTrue(test.usesObjects(Hi there, new Float(18), 42));Mockito.reset(test);when(test.usesPrimitives(anyInt(), anyFloat(), anyDouble(), anyByte(), anyBoolean())).thenReturn(true);assertTrue(test.usesPrimitives(1, 43.4f, 3.141592654d, (byte)2, false));Mockito.reset(test);// Gives unchecked type conversion warningwhen(test.usesCollections(anyList(), anyMap(), anySet())).thenReturn(true);assertTrue(test.usesCollections(Arrays.asList(Hello, World), Collections.EMPTY_MAP, Collections.EMPTY_SET));Mockito.reset(test);// Gives no warningwhen(test.usesCollections(anyListOf(String.class), anyMapOf(Integer.class, String.class), anySetOf(Object.class))).thenReturn(true);assertTrue(test.usesCollections(Collections.emptyList(), Collections.emptyMap(), Collections.emptySet()));Mockito.reset(test);// eq() must match exactlywhen(test.usesObjects(eq(Hello World), any(Object.class),anyInt())).thenReturn(true);assertFalse(test.usesObjects(Hi World, new Object(), 360));assertTrue(test.usesObjects(Hello World, new Object(), 360));Mockito.reset(test);when(test.usesString(startsWith(Hello))).thenReturn(true);assertTrue(test.usesString(Hello there));Mockito.reset(test);when(test.usesString(endsWith(something))).thenReturn(true);assertTrue(test.usesString(isnt that something));Mockito.reset(test);when(test.usesString(contains(second))).thenReturn(true);assertTrue(test.usesString(first, second, third.));Mockito.reset(test);// Regular Expressionwhen(test.usesString(matches(^\\\\w$))).thenReturn(true);assertTrue(test.usesString(Weak_Password1));assertFalse(test.usesString(Str0nG!pa$$woR%42));Mockito.reset(test);when(test.usesString((String)isNull())).thenReturn(true);assertTrue(test.usesString(null));Mockito.reset(test);when(test.usesString((String)isNotNull())).thenReturn(true);assertTrue(test.usesString(Anything));Mockito.reset(test);// Object ReferenceString string1 new String(hello);String string2 new String(hello);when(test.usesString(same(string1))).thenReturn(true);assertTrue(test.usesString(string1));assertFalse(test.usesString(string2));Mockito.reset(test);// Compare to eq()when(test.usesString(eq(string1))).thenReturn(true);assertTrue(test.usesString(string1));assertTrue(test.usesString(string2));Mockito.reset(test);when(test.usesVarargs(anyVararg())).thenReturn(true);assertTrue(test.usesVarargs(A,B,C,D,E));assertTrue(test.usesVarargs(ABC, 123));assertTrue(test.usesVarargs(Hello!));Mockito.reset(test);when(test.usesObject(isA(String.class))).thenReturn(true);assertTrue(test.usesObject(A String Object));assertFalse(test.usesObject(new Integer(7)));Mockito.reset(test);// Field equality using reflectionwhen(test.usesObject(refEq(new SomeBeanWithoutEquals(abc, 123)))).thenReturn(true);assertTrue(test.usesObject(new SomeBeanWithoutEquals(abc, 123)));Mockito.reset(test);// Compare to eq()when(test.usesObject(eq(new SomeBeanWithoutEquals(abc, 123)))).thenReturn(true);assertFalse(test.usesObject(new SomeBeanWithoutEquals(abc, 123)));Mockito.reset(test);when(test.usesObject(eq(new SomeBeanWithEquals(abc, 123)))).thenReturn(true);assertTrue(test.usesObject(new SomeBeanWithEquals(abc, 123)));Mockito.reset(test);}public class SomeBeanWithoutEquals {private String string;private int number;public SomeBeanWithoutEquals(String string, int number) {this.string string;this.number number;}}public class SomeBeanWithEquals {private String string;private int number;public SomeBeanWithEquals(String string, int number) {this.string string;this.number number;}Overridepublic int hashCode() {final int prime 31;int result 1;result prime * result getOuterType().hashCode();result prime * result number;result prime * result ((string null) ? 0 : string.hashCode());return result;}Overridepublic boolean equals(Object obj) {if (this obj)return true;if (obj null)return false;if (getClass() ! obj.getClass())return false;SomeBeanWithEquals other (SomeBeanWithEquals) obj;if (!getOuterType().equals(other.getOuterType()))return false;if (number ! other.number)return false;if (string null) {if (other.string ! null)return false;} else if (!string.equals(other.string))return false;return true;}private MatchersTest getOuterType() {return MatchersTest.this;}}
} 还可以通过扩展org.mockito.ArgumentMatcher来创建自己的org.mockito.ArgumentMatcher 。 让我们创建一个匹配器如果List包含特定元素该匹配器将触发。 我们还将创建用于创建Matcher的静态便利方法该方法使用argThat将Matcher转换为List以便在存根调用中使用。 我们将实现matches()方法来调用List的contains方法来进行实际的contains检查。 public class ListContainsMatcherT extends ArgumentMatcherListT {private T element;public ListContainsMatcher(T element) {this.element element;}Overridepublic boolean matches(Object argument) {SuppressWarnings(unchecked)ListT list (ListT) argument;return list.contains(element);}public static T ListT contains(T element) {return argThat(new ListContainsMatcher(element));}
} 现在进行测试以演示我们的新Matcher的运行 RunWith(MockitoJUnitRunner.class)
public class ListContainsMatcherTest {public interface TestClass {public boolean usesStrings(ListString list);public boolean usesIntegers(ListInteger list);}private ListString stringList Arrays.asList(Hello, Java, Code, Geek);private ListInteger integerList Arrays.asList(1, 2, 3, 4, 5);MockTestClass test;Testpublic void test() throws Exception {when(test.usesStrings(contains(Java))).thenReturn(true);when(test.usesIntegers(contains(5))).thenReturn(true);assertTrue(test.usesIntegers(integerList));assertTrue(test.usesStrings(stringList));Mockito.reset(test);when(test.usesStrings(contains(Something Else))).thenReturn(true);when(test.usesIntegers(contains(42))).thenReturn(true);assertFalse(test.usesStrings(stringList));assertFalse(test.usesIntegers(integerList));Mockito.reset(test);}
} 作为练习尝试编写自己的Matcher如果Map包含特定的键/值对该Matcher将匹配。 6.间谍和部分存根 如前所述可以使用Spy批注对类进行部分存根。 部分存根允许我们在测试中使用真实的类而仅存根与我们有关的特定行为。 Mockito准则告诉我们在处理遗留代码时通常应谨慎偶尔使用间谍。 最佳实践不是使用Spy部分模拟被测类而是部分模拟依赖项。 被测类应始终是真实对象。 假设我们正在处理一个在java.awt.BufferedImage上工作的图像处理类。 此类将BufferedImage放入其构造函数中并公开一个方法该方法使用随机彩色的垂直条纹填充图像并根据输入的缩略图高度返回图像的缩略图。 public class ImageProcessor {private BufferedImage image;public ImageProcessor(BufferedImage image) {this.image image;}public Image overwriteImageWithStripesAndReturnThumbnail(int thumbHeight) {debugOutputColorSpace();Random random new Random();Color color new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255));for (int x 0; x image.getWidth(); x) {if (x % 20 0) {color new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255));for (int y 0; y image.getHeight(); y) {image.setRGB(x, y, color.getRGB());}}}Image thumbnail image.getScaledInstance(-1, thumbHeight, Image.SCALE_FAST);Image microScale image.getScaledInstance(-1, 5, Image.SCALE_DEFAULT);debugOutput(microScale);return thumbnail;}private void debugOutput(Image microScale) {System.out.println(Runtime type of microScale Image is microScale.getClass());}private void debugOutputColorSpace() {for (int i0; i image.getColorModel().getColorSpace().getNumComponents(); i) {String componentName image.getColorModel().getColorSpace().getName(i);System.out.println(String.format(Colorspace Component[%d]: %s, i, componentName));}}
} overwriteImageWithStripesAndReturnThumbnail()方法中发生了很多事情。 它要做的第一件事是输出一些有关图像色彩空间的调试信息。 然后它会使用图像的宽度和高度方法生成一些随机颜色并将其绘制为整个图像中的水平条纹。 然后它执行缩放操作以返回代表缩略图的图像。 然后它进行第二次缩放操作以生成一个小的诊断微映像并输出此微映像的运行时类类型作为调试信息。 我们看到了与BufferedImage的许多交互其中大多数是完全内部的或随机的。 最终当我们要验证方法的行为时对我们而言重要的是对getScaledInstance()的首次调用-如果方法的返回值是从getScaledInstance返回的对象则我们的类起作用。 这是BufferedImage的行为它对我们来说很重要。 我们面临的问题是对BufferedImages方法还有许多其他调用。 从测试的角度来看我们实际上并不在乎这些方法的返回值但是如果我们不对它们的行为进行编码则它们将以某种方式导致NullPointerException并可能导致其他不良行为。 为了解决这个问题我们将为BufferedImage创建一个Spy并且仅对我们感兴趣的getScaledInstance()方法进行存根处理。 让我们创建一个空的测试类其中包含正在测试和创建Spy的类以及用于返回缩略图的Mock。 RunWith(MockitoJUnitRunner.class)
public class ImageProcessorTest {private ImageProcessor processor;Spyprivate BufferedImage imageSpy new BufferedImage(800, 600, BufferedImage.TYPE_INT_ARGB);MockImage mockThumbnail;Beforepublic void setup() {processor new ImageProcessor(imageSpy);}
} 请注意BufferedImage没有默认构造函数因此我们必须使用它的参数化构造函数自己实例化它如果它具有默认构造函数我们可以让Mockito为我们实例化它。 现在让我们首先尝试暂存我们感兴趣的行为。忽略输入高度宽度和模式并继续对所有三个参数使用Argument Matchers是有意义的。 我们最终得到如下内容 given(imageSpy.getScaledInstance(anyInt(), anyInt(), anyInt())).willReturn(mockThumbnail); 通常这是对Spy进行存根的最好方法但是在这种情况下会出现问题– imageSpy是一个实际的BufferedImage并且传递given() Given given()的存根调用是在存根操作执行时实际执行的真实方法调用由JVM运行。 getScaledInstance要求宽度和高度不为零因此此调用将导致引发IllegalArgumentException 。 一种可能的解决方案是在存根调用中使用实参 Testpublic void scale_should_return_internal_image_scaled() throws Exception {// Givengiven(imageSpy.getScaledInstance(-1, 100, Image.SCALE_FAST)).willReturn(mockThumbnail);// WhenImage actualImage processor.overwriteImageWithStripesAndReturnThumbnail(100);// ThenassertEquals(actualImage, mockThumbnail);} 该测试成功运行并在控制台上产生以下输出 Colorspace Component[0]: Red
Colorspace Component[1]: Green
Colorspace Component[2]: Blue
Runtime type of microScale Image is class sun.awt.image.ToolkitImage 使用实值的副作用是对getScaledInstance()的第二次调用getScaledInstance()用于创建用于调试的微图像无法匹配并且此时执行了BufferedImage中的real方法而不是我们的存根行为–这就是为什么我们看到真实值输出的微图像的运行时类型而不是Mockito模拟实现我们将查看是否将嘲笑缩略图传递给了调试输出方法。 但是如果我们想继续使用参数匹配器怎么办 可以使用doReturn()方法如果您记得通常用于void方法对getScaledInstance()方法进行存根而无需在存根时实际调用它。 Testpublic void scale_should_return_internal_image_scaled_doReturn() throws Exception {// GivendoReturn(mockThumbnail).when(imageSpy).getScaledInstance(anyInt(), anyInt(), anyInt());// WhenImage actualImage processor.overwriteImageWithStripesAndReturnThumbnail(100);// ThenassertEquals(actualImage, mockThumbnail);} 这给出以下输出 Colorspace Component[0]: Red
Colorspace Component[1]: Green
Colorspace Component[2]: Blue
Runtime type of microScale Image is class $java.awt.Image$$EnhancerByMockitoWithCGLIB$$72355119 您可以看到微映像的运行时类型现在是Mockito创建的Mock实现。 之所以如此是因为两个对getScaledInstance调用getScaledInstance与存根参数匹配因此两个调用都返回了Mock缩略图。 有一种方法可以确保在第二个实例中调用Spy的真实方法方法是使用doCallRealMethod()方法。 像往常一样Mockito让您将存根方法链接在一起以便为与存根参数匹配的存根方法的连续调用编写不同的行为。 Testpublic void scale_should_return_internal_image_scaled_doReturn_doCallRealMethod() throws Exception {// GivendoReturn(mockThumbnail).doCallRealMethod().when(imageSpy).getScaledInstance(anyInt(), anyInt(), anyInt());// WhenImage actualImage processor.overwriteImageWithStripesAndReturnThumbnail(100);// ThenassertEquals(actualImage, mockThumbnail);} 给出以下输出 Colorspace Component[0]: Red
Colorspace Component[1]: Green
Colorspace Component[2]: Blue
Runtime type of microScale Image is class sun.awt.image.ToolkitImage7.结论 我们已经研究了许多针对嘲笑和间谍的举止行为的方法并且暗示存在一种几乎无限的举止行为的方法。 Mockito的javadoc是有关Stubbing方法特别是Mockito开箱即用的ArgumentMatchers的良好信息来源。 我们已经详细介绍了存根行为在下一个教程中我们将研究使用Mockito验证框架来验证Mocks的行为。 8.下载源代码 这是关于Mockito Stubbing的课程。 您可以在此处下载源代码 mockito2-stubbing 翻译自: https://www.javacodegeeks.com/2015/11/mocks-spies-partial-mocks-and-stubbing.html