mockito从入门到通关

简介: 有很多朋友不愿意写单元测试,觉得写单测试比较花时间,甚至不会写单元测试,很大程度上是因为不想写或者不会写mock。

为什么要mock?


有很多朋友不愿意写单元测试,觉得写单测试比较花时间,甚至不会写单元测试,很大程度上是因为不想写或者不会写mock。


mock对于单元测试来很重要。单元测试之所以名字里面有“单元”,就是因为一个测试用例只测很小的一个单元代码。但我们的代码总会有依赖,要测试的方法内部可能调用了其它类的方法,这在代码中是很常见的逻辑。所以我们经常会需要mock,用来消除依赖。


所谓mock,翻译过来就是“模拟”,就是模拟你要测试的代码里面「依赖的其它对象」,模拟它的输入和输出,这样就不用管其它逻辑是如何实现的,我们「假定它是符合我们期望的就行了」。


可能你会有一个疑问:那我们在单元测试里面假定它符合期望了,但实际运行时它有bug了,并不是符合我们期望的,怎么办呢?

首先,我们对那个类也会做单元测试,我们用它的单元测试保证了它的逻辑是正确的。

然后,一个完整的测试体系,不应该只有单元测试。单元测试之上,还应该有集成测试、API测试等,从更高的层面来保证整个应用程序能够如预期工作。


Mockito的基本用法


Mockito是Java语言非常主流的一个框架,自己使用起来感觉也比较好用。所以这篇文章想汇总介绍一下Mockito的各种用法,这样大家在以后写单元测试和看单元测试的时候,就能够比较清晰为什么要这么写。

设置Mockito环境

要使用Mockito,首先得在测试类里面设置好Mockito环境。这是为了能够让单元测试框架(本文主要介绍JUnit)能够识别和使用Mockito。

在JUnit 4, JUnit使用了@RunWith注解来声明一个“运行器”。这个运行期的作用是为单元测试提供「mock的初始化工作」(比如使用@Mock、@Spy等注解时,需要初始化),以及「验证mock语法」的功能。

比如我们可能会经常用到的:

@RunWith(JUnit4.class)
@RunWith(SpringRunner.class)
@RunWith(SpringJUnit4ClassRunner.class)

Mockito也有相应的启动器,在@RunWith注解上面使用这个启动器就可以使用Mockito的环境了:

@RunWith(MockitoJUnitRunner)

在JUnit 5,使用了@ExtendWith注解来代替@RunWith注解,Mockito也支持JUnit 5,提供了MockitoExtension类。

除了使用注解以外,也可以使用静态方法initMocks来实现这个功能:

MockitoAnnotations.initMocks(this) 

mock

mock,即mock一个对象。也是也注解和代码两种方式可以实现。

@Mock
private User user;
Order order = Mockito.mock(Order.class);

mock对象后,就可以对它使用given等方法模拟它的输入和输出。

given(order.getId()).willReturn(1L);
assertEquals(order.getId(), 1L);

spy

mock出来的对象是完全虚拟的,不会真正地调用本来的实现。如果不对它使用given等方法,会返回默认值(null, 0, false等)。而spy如果不使用given等方法,会调用这个对象本来的实现,返回实际运行后的值。

❝

不是很推荐使用spy,因为它没有消除依赖

❞

spy同样有注解和静态方法的方式:

@Spy
private user user;
Order order = Mockito.spy(Order.class);

captor

captor翻译过来是“捕获”的意思,主要用来捕捉程序运行时调用mock或者spy的对象的方法时,传入的参数。它支持泛型,即要捕获的参数的类型。

@Captor 
ArgumentCaptor<User> userCaptor;
ArgumentCaptor<String> arg = ArgumentCaptor.forClass(String.class);

captor一般是与given或者verify等方法配合使用。

@Mock
List mockedList;
@Captor 
ArgumentCaptor argCaptor;
@Test
public void whenUseCaptorAnnotation_thenTheSam() {
    mockedList.add("one");
    Mockito.verify(mockedList).add(argCaptor.capture());
    assertEquals("one", argCaptor.getValue());
}

InjectMocks

使用@InjectMocks注解,可以将mock或spy的对象自动注入要测试的对象。这在Spring等使用自动注入的框架里用得非常广泛。

@Mock
Map<String, String> wordMap;
@InjectMocks
MyDictionary dic = new MyDictionary();
// 类定义:
class MyDictionary {
    Map<String, String> wordMap;
    public MyDictionary() {
        wordMap = new HashMap<String, String>();
    }
    public void add(final String word, final String meaning) {
        wordMap.put(word, meaning);
    }
    public String getMeaning(final String word) {
        return wordMap.get(word);
    }
}


打桩


所谓打桩,其实就是mock的一个过程。我们给定期望的输入和输入,mock的对象就能够如我们期望的那样工作。

打桩有两种写法,一种是传统的写法,一种是BDD **Behavior-Driven Development (行为驱动开发)**的写法。

传统写法

我们先看看传统的写法,传统的写法主要用Mockito类的方法。基本是when-then-invoke-verify形式。

when(phoneBookRepository.contains(momContactName))
  .thenReturn(false);
phoneBookService.register(momContactName, momPhoneNumber);
verify(phoneBookRepository)
  .insert(momContactName, momPhoneNumber);

BDD写法

BDD写法主要是用BDDMockito类的方法,看起来是given–will-invoke-then的形式。

given(phoneBookRepository.contains(momContactName))
  .willReturn(false);
phoneBookService.register(momContactName, momPhoneNumber);
then(phoneBookRepository)
  .should()
  .insert(momContactName, momPhoneNumber);

动态mock

有时候我们可能会遇到这个问题:在方法内部可能有循环或者递归,会多次调用其它对象的方法,但输入的参数不同,我们在mock的时候,期望它根据不同的输入参数返回不同的结果。这个时候如果一个一个写given,就需要写很多次。但其实可以用动态mock来做。它是基于InvocationOnMock来实现的。

given(phoneBookRepository.contains(momContactName))
  .willReturn(true);
given(phoneBookRepository.getPhoneNumberByContactName(momContactName))
  .will((InvocationOnMock invocation) ->
    invocation.getArgument(0).equals(momContactName) 
      ? momPhoneNumber 
      : null);
phoneBookService.search(momContactName);
then(phoneBookRepository)
  .should()
  .getPhoneNumberByContactName(momContactName);


Mockito的不足

Mockito可以说是Java语言最流行的mock框架了。但它不是所有对象都可以mock的,有一些限制。

Mockito在3.4.0以前,是不能mock静态方法的。这取决于它的底层实现,是使用动态代理来做的。而动态代理是代理静态方法、final方法和private方法的。

一般来说,我们不应该mock静态方法、final方法和private方法的。但有时候可能会有这样的需求,比如apache和guava框架,就使用了大量的静态方法提供一些工具类。如果需要Mock的话,就得配合PowerMock等框架来实现。但PowerMock框架目前还不支持JUnit 5。

在Mockito 3.4.0,开始支持mock静态方法,底层是通过修改字节码来实现的。但性能很差,mock一个实例大概要一秒多,大家酌情使用。

目录
相关文章
|
机器学习/深度学习 算法 数据库
Dataset之LFW:LFW人脸数据库的简介、安装、使用方法之详细攻略
Dataset之LFW:LFW人脸数据库的简介、安装、使用方法之详细攻略
Dataset之LFW:LFW人脸数据库的简介、安装、使用方法之详细攻略
|
7月前
|
人工智能 Linux API
【最全】零基础玩转OpenClaw:阿里云/本地部署步骤+Skill清单+免费API配置一站式教程
OpenClaw(原Clawdbot)并非简单的AI聊天机器人,而是一套具备任务自动化、跨平台协作能力的AI操作系统,Skill作为其核心扩展能力,能让AI真正实现“动手干活”。2026年的OpenClaw生态已拥有3000+Skill,覆盖30余个应用领域,无论是个人提升办公效率,还是团队实现轻量协作,都能通过合理配置满足需求。本文将从零基础部署入手,完整讲解阿里云、MacOS、Linux、Windows11的OpenClaw部署流程,搭配阿里云百炼API配置方法、必装Skill清单及常见问题解答,让新手也能快速上手,真正让AI落地工作场景。
894 3
|
11月前
|
人工智能 数据可视化 测试技术
提升测试效率5倍!Dify驱动的可视化工作流实现自动化测试“开箱即用”
本文介绍如何利用Dify可视化工作流快速构建自动化测试体系,涵盖用例生成、API测试和UI测试等核心场景。通过拖拽式设计降低技术门槛,显著提升测试效率与覆盖率,助力团队实现质量保障的智能化转型。
|
XML Java 测试技术
使用 Spring 的 @Import 和 @ImportResource 注解构建模块化应用程序
本文介绍了Spring框架中的两个重要注解`@Import`和`@ImportResource`,它们在模块化开发中起着关键作用。文章详细分析了这两个注解的功能、使用场景及最佳实践,帮助开发者构建更清晰、可维护和可扩展的Java应用程序。
552 0
|
域名解析 人工智能 缓存
无前端经验如何快速搭建游戏站:使用 windsurf 从零到上线的详细指南
本指南涵盖游戏站页面初稿设计、工具配置、内容设计与功能实现及部署上线的全流程。通过参考优秀网站设计,利用v0.dev平台完成页面布局和样式调整,并下载代码进行后续开发。使用Windsurf配置工作空间规则,确保以用户易懂的方式推进项目。逐步实现多语言支持、favicon设置、嵌入游戏等功能,确保网页专业且用户体验良好。最后通过购买域名、GitHub托管代码、Vercel部署等步骤将游戏站成功上线。
1014 10
|
项目管理 Python
如何在Mac上安装多个Python环境
在你的Mac上使用多个Python环境可以对项目管理很有帮助,特别是在同时处理不同Python版本或不同的包需求时。在这篇文章中,我们将向你展示如何在Mac上轻松地安装和管理多个Python环境。
934 5
 如何在Mac上安装多个Python环境
|
Java 测试技术 持续交付
Springboot中JUNIT5单元测试+Mockito详解
Springboot中JUNIT5单元测试+Mockito详解
2437 1
|
存储 Java Spring
深入理解org.springframework.web.context.request.RequestContextHolder
`RequestContextHolder`是Spring提供的一个便捷工具类,用于在非Controller层访问请求上下文信息。通过理解其工作原理和应用场景,可以更好地在Spring应用中管理和使用请求信息,提升代码的可维护性和扩展性。
1242 0
|
Java Apache
BeanUtils.copyProperties()用法总结
BeanUtils.copyProperties()用法总结
1354 1
|
安全 Java API
Java 8 流库的魔法革命:Filter、Map、FlatMap 和 Optional 如何颠覆编程世界!
【8月更文挑战第29天】Java 8 的 Stream API 通过 Filter、Map、FlatMap 和 Optional 等操作,提供了高效、简洁的数据集合处理方式。Filter 用于筛选符合条件的元素;Map 对元素进行转换;FlatMap 将多个流扁平化合并;Optional 安全处理空值。这些操作结合使用,能够显著提升代码的可读性和简洁性,使数据处理更为高效和便捷。
803 0

热门文章

最新文章