cks子,间谍,局部Mo子和短管

本文是我们名为“ 用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 Mock,Partial 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 Optional<String> 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);// WhenOptional<String> actualBuffer = processor.statusAndTest();// ThenassertFalse(actualBuffer.isPresent());}
}

我们知道statusAndTest()将涉及对PrinterprintTestPage()方法的调用,并且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);// WhenOptional<String> 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);// WhenOptional<String> 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();// WhenOptional<String> 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 Optional<String> 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();// WhenOptional<String> actualBuffer = processor.statusAndTest();// ThenassertFalse(actualBuffer.isPresent());}

在这里,我们看到可以使用doThrow()抛出所需的任何异常。 在这种情况下,我们将抛出满足我们测试要求的PrinterNotConnectedException

现在我们已经学习了如何对void方法进行存根,让我们看一下返回一些数据。

4.存根返回值

让我们开始创建一个数据访问对象,以持久性地从数据库中检索客户对象。 该DAO将使用内部的企业java EntityManager接口进行实际的数据库交互。

为了使用EntityManager我们将使用JPA 2.0的Hibernate实现,将以下依赖项添加到pom.xml中:

<dependency><groupId>org.hibernate.javax.persistence</groupId><artifactId>hibernate-jpa-2.0-api</artifactId><version>1.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 Optional<Customer> 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);// WhenOptional<Customer> 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.classexpectedId ,然后返回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);// WhenOptional<Customer> 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);// WhenOptional<Customer> actualCustomer1 = dao.findById(expectedId1);Optional<Customer> 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);// WhenOptional<Customer> actualCustomer1 = dao.findById(expectedId1);Optional<Customer> 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);// WhenOptional<Customer> 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);// WhenOptional<Customer> actualCustomer1 = dao.findById(expectedId);Optional<Customer> actualCustomer2 = dao.findById(expectedId);// ThenassertTrue(actualCustomer1.isPresent());assertFalse(actualCustomer2.isPresent());}

如果我们的find方法由于某些持久性问题而引发异常怎么办? 让我们测试一下!

@Test(expected=IllegalArgumentException.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 Answer<Customer> withCustomerById = new Answer<Customer>() {@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);// WhenOptional<Customer> actualCustomer0 = dao.findById(expectedId[0]);Optional<Customer> actualCustomer1 = dao.findById(expectedId[1]);Optional<Customer> 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);// WhenOptional<Customer> 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收藏夹

图1 – Content Assist收藏夹

使用多个模拟

现在,我们将结合在一起使用多个模拟。 让我们向DAO中添加一个方法以返回所有可用客户的列表。

public List<Customer> findAll() throws Exception {TypedQuery<Customer> query = em.createQuery("select * from CUSTOMER", Customer.class);return query.getResultList();}

在这里,我们看到EntityManagercreateQuery()方法返回一个通用类型TypedQuery 。 它接受一个SQL String和一个作为返回类型的类作为参数。 TypedQuery本身公开了几种方法,包括List getResultList() ,这些方法可用于执行返回多个值的查询,例如上面的select * from CUSTOMER查询中的select * from CUSTOMER

为了对此方法编写测试,我们将要创建一个TypedQuery的Mock。

@Mock
private TypedQuery<Customer> 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);// WhenList<Customer> 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(List<String> list, Map<Integer, String> map, Set<Object> 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("isn't 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()方法来调用Listcontains方法来进行实际的contains检查。

public class ListContainsMatcher<T> extends ArgumentMatcher<List<T>> {private T element;public ListContainsMatcher(T element) {this.element = element;}@Overridepublic boolean matches(Object argument) {@SuppressWarnings("unchecked")List<T> list = (List<T>) argument;return list.contains(element);}public static <T> List<T> contains(T element) {return argThat(new ListContainsMatcher<>(element));}
}

现在进行测试,以演示我们的新Matcher的运行!

@RunWith(MockitoJUnitRunner.class)
public class ListContainsMatcherTest {public interface TestClass {public boolean usesStrings(List<String> list);public boolean usesIntegers(List<Integer> list);}private List<String> stringList = Arrays.asList("Hello", "Java", "Code", "Geek");private List<Integer> 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 i=0; 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.ToolkitImage

7.结论

我们已经研究了许多针对嘲笑和间谍的举止行为的方法,并且暗示存在一种几乎无限的举止行为的方法。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/356257.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

8数据提供什么掩膜产品_工业轨式1-8路RS485数据(MODBUS RTU协议)厂家产品说明...

产品描述工业级数点对点光猫提供1-8路RS485&#xff08;MODBUS RTU协议&#xff09;&#xff1b;在光纤中传输&#xff0c;该产品突破了传统串行接口通讯距离与通讯速率的矛盾&#xff0c;同时&#xff0c;也解决了电磁干扰、地环干扰和雷电破坏的难题&#xff0c;大大提高了数…

BZOJ 4568 倍增维护线性基

在树的路径上选取一些点 使得这些点权xor后的结果最大 思路&#xff1a; 时限60s 59696ms卡过去了哈哈哈 //By SiriusRen #include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef long long ll; const int N20005; ll T…

代码android点击效果,GitHub - likaiyuan559/TouchEffects: Android View点击特效TouchEffects,几行代码为所有控件添加点击效果...

Android 点击特效TouchEffectsTouchEffects能够帮助你更快速方便的增加点击时候的效果&#xff0c;TouchEffects的目标是打造一个稳定、全面、且能更方便的自定义及个性化的一款点击效果框架。功能特点&#xff1a;只需要几行代码便能为全局的View加上点击效果支持多种点击效果…

Linux下暴力破解工具Hydra详解

Linux下暴力破解工具Hydra详解 一、简介 Number one of the biggest security holes are passwords, as every password security study shows. Hydra is a parallized login cracker which supports numerous protocols to attack. New modules are easy to add, beside that,…

android的json解析方式,Android解析JSON方式

JSON数据格式的定义&#xff1a;JSON的全称是&#xff1a;JavaScript Object Notation&#xff0c;是一种轻量级的数据交换格式。它构建于两种结构&#xff1a;1、"名称/值" 对的集合(a collection of name / value pairs)。不同的语言中&#xff0c;它被理解为对象(…

uniapp无法使用substr_关公战秦琼------Excel、SPSS Modler和R的使用对比(下)

5. 数据可视化Excel在Excel中对数据可视化除常用的图表外&#xff0c;还有样式、迷你图等直接和实际数据来共同展示效果。另外&#xff0c;还可以与切片器、PowerView等功能结合来动态可视化。Excel中图表的类型很多&#xff0c;如常见的散点图、折线图、柱形图、饼图等&#x…

第三次预作业20155231邵煜楠:虚拟机上的Linux学习

java第三次预作业——虚拟机初体验(学习记录) 学习在笔记本上安装Linux操作系统通过老师给予的官网链接&#xff0c;下载了VirtualBox-5.1.14-112924-win和Ubuntu-16.04-desktop-amd64.iso开始按照安装&#xff0c;尽管如此&#xff0c;还是遇到来了一些问题。 -在选择虚拟机的…

miui11未升级android版本,MIUI 11稳定版正式推送,升级前你需要了解

原标题&#xff1a;MIUI 11稳定版正式推送&#xff0c;升级前你需要了解10月18日&#xff0c;MIUI官网正式宣布MIUI 11 正式推送&#xff0c;这是在小米MIX Alpha发布会之时同时推出的&#xff0c;在经过内测、公测后正式面向大众消费者推送。以下是一些您升级前需要了解的东西…

lumion自动保存_LUMION吊打MAX,轻松玩转规划渲染!

--文末获取&#xff1a;城市配景建筑模型包--大型场景渲染是LUMION渲染中难度最高的图纸首先&#xff0c;需要大量非常优质的配景包括&#xff1a;配景建筑、植物其次&#xff0c;对硬件要求也极高像这样的图纸渲染主体建筑周边需要布置大量的配景建筑没有个2080TI&#xff0c;…

在地图上 添加柱状_如何做出一份惊艳的PPT地图页?这2个字,你一定要知道!...

这篇文章&#xff0c;咱们就从一个读者私信给我的案例来说&#xff0c;就是这张PPT&#xff1a;他当时问我的问题是&#xff0c;怎么把这个表格优化的更美观&#xff1f;坦白讲&#xff0c;这是一个无解的问题&#xff0c;为什么这么说呢&#xff1f;咱们简单对这个图表分析&am…

akka与neety_Akka STM –与STM Ref和Agent一起打乒乓球

akka与neety乒乓是一个经典示例&#xff0c;其中2个玩家&#xff08;或线程&#xff09;访问共享资源–乒乓表并在彼此之间传递Ball&#xff08;状态变量&#xff09;。 使用任何共享资源&#xff0c;除非我们同步访问&#xff0c;否则线程可能会遇到潜在的死锁情况。 PingPong…

怎样调用另一个html中的元素,html – 使用DIV作为另一个元素的背景

这里我用2个div做了一个例子&#xff1a;> .content,包含前端所需的一切> .background – 包含文本,图像和背景中的所有其他内容要将一个div包装在另一个div上(制作叠加层),你必须将它们放入相同的元素中,在本例中它是#wrapper div.放置位置&#xff1a;包装的相对宽度/高…

在running android lint期间发生了内部错误.,Eclipse出现quot;Running Android Lint has encountered a problemquot...

近期打开Eclipse的时候&#xff0c;总是发生这种一个错误&#xff1a;"Running Android Lint has encountered a problem"。截图例如以下&#xff1a;。可是Eclipse能够正常执行程序。不会造成其它影响。可是每次打开Eclipse时。总是看到这个警告&#xff0c;心情会非…

idea 导出war包_使用IDEA实现远程代码DEBUG调试教程详解

我们在使用 IDEA DEBUG调试代码的时候&#xff0c;常常见到控制台会输出一句这样的话&#xff1a;「Connected to the target VM, address: 127.0.0.1:62981, transport: socket」&#xff0c;所以即使代码不在本地运行&#xff0c;只要JVM打开调试模式&#xff0c;并且网络能够…

鸿蒙os2.0发布回放,华为HarmonyOS 2.0系统发布会内容大全 鸿蒙os6月2日直播回放地址入口...

华为鸿蒙os6月2日直播回放在哪里看&#xff1f;华为HarmonyOS 2系统发布会说了些啥&#xff1f;HarmonyOS 2系统在6月2日正式发布了&#xff0c;之前预约过的小伙伴现在就可以更新体验了&#xff0c;这次发布将会对安卓系统一个重大的革新&#xff0c;将会彻底颠覆之前的安卓系…

python中当地时间_R 与 Python 中与时间相关内容的梳理

约万字长文预警&#xff0c;如果你没时间&#xff0c;就不用看了&#xff0c;这就是一个梳理的文章&#xff0c;方便我后来找资料。因为工作的关系&#xff0c;近期需要梳理一些 Python 的知识(可能有小伙伴知道了&#xff0c;LI-6800 搞了个大动作&#xff0c;支持 Python 编程…

JavaOne 2015:为JDK 9做准备– blog @ CodeFX

JavaOne 2015看到了Project Jigsaw团队关于Java 9中的模块化的一系列讨论 。它们都是非常有趣的&#xff0c;并且充满了宝贵的信息&#xff0c;我敦促每个Java开发人员都注意它们。 除此之外&#xff0c;我想给社区一种搜索和引用它们的方法&#xff0c;因此我在这里总结一下&…

华为oj----iNOC产品部-杨辉三角的变形 .

此题提供三种方法&#xff0c;第一种&#xff0c;一开始就能想到的&#xff0c;设置一个足够大的数组存储生成的杨辉三角&#xff0c;然后进行判断就行,此方法参见&#xff1a;华为oj iNOC产品部-杨辉三角的变形 另一种方法是采用递归&#xff1a; 三角形的每行的个数为2*n-1,n…

java 短路判断_java中和的区别(|和|同理)

在java的基础面试题中肯定有这个问题&#xff1a;&和&&的区别&#xff1f;这个问题很好回答&#xff0c;核心思想就是一个不短路一个短路的问题。首先说下&&#xff0c;1、它可以表示二进制中的位运算分析&#xff1a;二元操作符&#xff0c;操作两个二进制数据…

不属于计算机完成科学特点的是,2020年9月网络教育统考计算机应用基础模拟题试卷4...

2020年9月网络教育统考计算机应用基础模拟题试卷4一、单选题1.______是第四代计算机的典型代表。A.微型机B.超小型机C.巨型机D.大中型机答案&#xff1a;A2.个人计算机又称PC机&#xff0c;这种计算机属于______。A.微型计算机B.小型计算机C.超级计算机D.巨型计算机答案&#x…