1.介绍

Fluent api是一种基于方法链的软件工程设计技术,用于构建简洁、易读、流畅的界面。

他们经常用于建筑商,工厂和其他创建型设计模式。最近,它们变得越来越受欢迎随着演变Java可以在流行的api中找到,例如Java流APIMockito.测试框架。

然而,模拟Fluent api可能是痛苦的,因为我们经常需要设置复杂的模拟对象层次结构

在本教程中,我们将看看我们如何使用Mockito的一个很好的特征来避免这种情况。

2.一个简单的流畅API

在本教程中,我们将使用构建器设计模式来演示用于构造披萨对象的简单流畅API:

Pizza Pizza = new Pizza .PizzaBuilder("Margherita") .size(PizzaSize.LARGE) . withextatpingon ("Mushroom") .withStuffedCrust(false) .willCollect(true) .applyDiscount(20) .build();

正如我们所看到的,我们已经创建了一个易于理解的API,它读起来像DSL允许我们创建比萨具有各种特征的对象。

现在,我们将定义一个使用构建器的简单服务类。这将是我们稍后将要测试的类:

公共类Pizzaservice {Private Pizza.Pizzabuilder Builder;Public Pizzaservice(Pizza.Pizzabuilder Builder){this.builder = builder;Public Pizza OrderHouseSpecial(){return builder.name(“特殊”).size(pizzasize.large).withextratopping(“蘑菇”).withstuffcrust(true).withextratopping(“chilli”).willcollect(true).applydiscount(true).applydiscount(20).Build();}}

我们的服务非常简单,包含一个被调用的方法orderHouseSpecial。顾名思义,我们可以使用此方法构建具有一些预定义的属性的特殊披萨。

3.传统的嘲笑

用传统的方式来模仿是否需要创建8个mockPizzaBuilder对象。我们需要一个模拟的PizzaBuilder返回的名称方法,然后是一个模拟PizzaBuilder返回的大小法等。我们将继续这种方式,直到满足我们流畅API链中的所有方法调用。

现在让我们看看如何使用常规Mockito编写单元测试来测试我们的服务方法模拟:

@test public viventraditOonmocking_whenserviceinvoked_thenpizzaisbuilt(){pizzabuilder namebuilder = mockito.mock(pizza.pizzabuilder.class);pizzabuilder sizebuilder = mockito.mock(pizza.pizzabuilder.class);pizzabuilder firsttoppingbuilder = mockito.mock(pizza.pizzabuilder.class);pizzabuilder switetoppingbuilder = mockito.mock(pizza.pizzabuilder.class);pizzabuilder stapedbuilder = mockito.mock(pizza.pizzabuilder.class);pizzabuilder willcollectbuilder = mockito.mock(pizza.pizzabuilder.class);Pizzabuilder折扣封信= mockito.mock(pizza.pizzabuilder.class);pizzabuilder builder = mockito.mock(pizza.pizzabuilder.class);什么时候(builder.name(anystring()))。然后return(namebuilder);什么时候(namebuilder.size(任何(pizza.pizzasize.class)))。然后return(sizeBuilder); when(sizeBuilder.withExtraTopping(anyString())).thenReturn(firstToppingBuilder); when(firstToppingBuilder.withStuffedCrust(anyBoolean())).thenReturn(stuffedBuilder); when(stuffedBuilder.withExtraTopping(anyString())).thenReturn(secondToppingBuilder); when(secondToppingBuilder.willCollect(anyBoolean())).thenReturn(willCollectBuilder); when(willCollectBuilder.applyDiscount(anyInt())).thenReturn(discountBuilder); when(discountBuilder.build()).thenReturn(expectedPizza); PizzaService service = new PizzaService(builder); Pizza pizza = service.orderHouseSpecial(); assertEquals("Expected Pizza", expectedPizza, pizza); verify(builder).name(stringCaptor.capture()); assertEquals("Pizza name: ", "Special", stringCaptor.getValue()); // rest of test verification }

在这个例子中,我们需要嘲笑PizzaBuilder我们提供给PizzaService。正如我们所看到的,这不是我们需要返回模拟的琐碎的任务,这将在流利API中返回每个呼叫的模拟。

这将导致复杂的模拟对象层次结构,难以理解,维护起来也很棘手。

4.深桩拯救

谢天谢地,Mockito提供了一个叫做的一个非常简洁的功能深存根这允许我们指定一个回答模式时创建一个mock

要做一个很深的存根,我们只需添加5。RETURNS_DEEP_STUBS当我们创建一个mock时,常量作为附加参数:

@test public vivendeepmocks_whenserviceinvoked_thenpizzaisbuilt(){pizzabuilder builder = mockito.mock(pizza.pizzabuilder.class,mockito.returns_deep_stubs);mockito.when(builder.name(anystring()).size(任何(pizza.pizzasize.class)).withextratopping(anystring()).withstuffedcrust(AnyBoolean()).willextratopping(AnyString()).willcollect(AnyBoolean()).ApplyDiscount(Anyint()).build()).thenreturn(预示性比奇);pizzaservice service = new pizzaservice(builder);Pizza Pizza = Service.OrderHouseSpecial();assertequals(“预计披萨”,预计,比萨饼);}

通过使用5。RETURNS_DEEP_STUBS争论,我们告诉Mockito做出一种深刻的模拟。这使得可以将完整方法链或在我们的案例中进行流利API进行嘲笑。

这将带来一个更优雅的解决方案和一个比我们在前一节中看到的测试更容易理解的测试。本质上,我们避免了创建复杂的模拟对象层次结构的需要。

我们也可以直接使用此答案模式@Mock注释:

@Mock(answer = answers . returns_deep_stub)私有pizzbuilder另一个builder;

需要注意的一点是,验证只适用于链中的最后一个mock。

5.结论

在这个快速教程中,我们看到了如何使用Mockito来模拟一个简单流畅的API。首先,我们研究了传统的模拟方法,并理解了与此方法相关的困难。

然后,我们研究了一个榜样,使用众所周知的Mockito称为Deep Stubs,这允许更优雅的方式来嘲笑我们的流利的API。

与往常一样,本文的完整源代码是可用的在github上

通用的底部

开始使用Spring 5和Spring Boot 2,通过学习的春天课程:

>>看看这个课程
评论在本文上关闭!