2023单元测试利器Mockito框架详解(超详细~)

这篇具有很好参考价值的文章主要介绍了2023单元测试利器Mockito框架详解(超详细~)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

 前言

Mockito 是当前最流行的 单元测试 Mock 框架。采用 Mock 框架,我们可以 虚拟 出一个 外部依赖,降低测试 组件 之间的 耦合度,只注重代码的 流程与结果,真正地实现测试目的。

正文

什么是Mock

Mock 的中文译为仿制的,模拟的,虚假的。对于测试框架来说,即构造出一个模拟/虚假的对象,使我们的测试能顺利进行下去。

Mock 测试就是在测试过程中,对于某些 不容易构造(如 HttpServletRequest 必须在 Servlet 容器中才能构造出来)或者不容易获取 比较复杂 的对象(如 JDBC 中的 ResultSet对象),用一个 虚拟 的对象(Mock 对象)来创建,以便测试方法。

为什么使用Mock测试

单元测试 是为了验证我们的代码运行正确性,我们注重的是代码的流程以及结果的正确与否。

对比真实运行代码,可能其中有一些 外部依赖 的构建步骤相对麻烦,如果我们还是按照真实代码的构建规则构造出外部依赖,会大大增加单元测试的工作,代码也会参杂太多非测试部分的内容,测试用例显得复杂难懂。

采用 Mock 框架,我们可以 虚拟 出一个 外部依赖,只注重代码的 流程与结果,真正地实现测试目的。

Mock测试框架的好处

  1. 可以很简单的虚拟出一个复杂对象(比如虚拟出一个接口的实现类);
  2. 可以配置 mock 对象的行为;
  3. 可以使测试用例只注重测试流程与结果;
  4. 减少外部类、系统和依赖给单元测试带来的耦合。

Mockito的流程

mock框架,经验分享,软件测试,自动化测试,单元测试,自动化测试,测试工程师,软件测试,Mock

如图所示,使用 Mockito 的大致流程如下:

  1. 创建 外部依赖 的 Mock 对象, 然后将此 Mock 对象注入到 测试类 中;
  2. 执行 测试代码
  3. 校验 测试代码 是否执行正确。

Mockito的使用

在 Module 的 build.gradle 中添加如下内容:

dependencies {
    //Mockito for unit tests
    testImplementation "org.mockito:mockito-core:2.+"
    //Mockito for Android tests
    androidTestImplementation 'org.mockito:mockito-android:2.+'
}

这里稍微解释下:

  • mockito-core: 用于 本地单元测试,其测试代码路径位于 module-name/src/test/java/
  • mockito-android: 用于 设备测试,即需要运行 android 设备进行测试,其测试代码路径位于 module-name/src/androidTest/java/
mockito-core最新版本可以在 Maven 中查询:mockito-core。 mockito-android最新版本可以在 Maven 中查询:mockito-android

Mockito的使用示例

普通单元测试使用 mockito(mockito-core),路径:module-name/src/test/java/

这里摘用官网的 Demo:

检验调对象相关行为是否被调用

import static org.mockito.Mockito.*;

// Mock creation
List mockedList = mock(List.class);

// Use mock object - it does not throw any "unexpected interaction" exception
mockedList.add("one"); //调用了add("one")行为
mockedList.clear(); //调用了clear()行为

// Selective, explicit, highly readable verification
verify(mockedList).add("one"); // 检验add("one")是否已被调用
verify(mockedList).clear(); // 检验clear()是否已被调用

这里 mock 了一个 List(这里只是为了用作 Demo 示例,通常对于 List 这种简单的类对象创建而言,直接 new 一个真实的对象即可,无需进行 mock),verify() 会检验对象是否在前面已经执行了相关行为,这里 mockedList 在 verify 之前已经执行了 add("one") 和 clear() 行为,所以verify() 会通过。

配置/方法行为

// you can mock concrete classes, not only interfaces
LinkedList mockedList = mock(LinkedList.class);
// stubbing appears before the actual execution
when(mockedList.get(0)).thenReturn("first");
// the following prints "first"
System.out.println(mockedList.get(0));
// the following prints "null" because get(999) was not stubbed
System.out.println(mockedList.get(999));

这里对几个比较重要的点进行解析:

when(mockedList.get(0)).thenReturn("first")

这句话 Mockito 会解析为:当对象 mockedList 调用 get()方法,并且参数为 0 时,返回结果为"first",这相当于定制了我们 mock 对象的行为结果(mock LinkedList 对象为 mockedList,指定其行为 get(0),则返回结果为 "first")。

mockedList.get(999)

由于 mockedList 没有指定 get(999) 的行为,所以其结果为 null。因为 Mockito 的底层原理是使用 cglib 动态生成一个 代理类对象,因此,mock 出来的对象其实质就是一个 代理,该代理在 没有配置/指定行为 的情况下,默认返回 空值

上面的 Demo 使用的是 静态方法 mock() 模拟出一个实例,我们还可以通过注解 @Mock 也模拟出一个实例:

@Mock
private Intent mIntent;

@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();

@Test
public void mockAndroid(){
    Intent intent = mockIntent();
    assertThat(intent.getAction()).isEqualTo("com.yn.test.mockito");
    assertThat(intent.getStringExtra("Name")).isEqualTo("Whyn");
}

private Intent mockIntent(){
    when(mIntent.getAction()).thenReturn("com.yn.test.mockito");
    when(mIntent.getStringExtra("Name")).thenReturn("Whyn");
    return mIntent;
}

对于标记有 @Mock@Spy@InjectMocks 等注解的成员变量的 初始化 到目前为止有 2 种方法:

  1. 对 JUnit 测试类添加 @RunWith(MockitoJUnitRunner.class)
  2. 在标示有 @Before 方法内调用初始化方法:MockitoAnnotations.initMocks(Object)

上面的测试用例,对于 @Mock 等注解的成员变量的初始化又多了一种方式 MockitoRule。规则 MockitoRule 会自动帮我们调用 MockitoAnnotations.initMocks(this) 去 实例化 出 注解 的成员变量,我们就无需手动进行初始化了。

Mockito的重要方法

实例化虚拟对象

// You can mock concrete classes, not just interfaces
LinkedList mockedList = mock(LinkedList.class);

// Stubbing
when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(1)).thenThrow(new RuntimeException());

// Following prints "first"
System.out.println(mockedList.get(0));
// Following throws runtime exception
System.out.println(mockedList.get(1));
// Following prints "null" because get(999) was not stubbed
System.out.println(mockedList.get(999));

// Although it is possible to verify a stubbed invocation, usually it's just redundant
// If your code cares what get(0) returns, then something else breaks (often even before verify() gets executed).
// If your code doesn't care what get(0) returns, then it should not be stubbed. Not convinced? See here.
verify(mockedList).get(0);
  • 对于所有方法,mock 对象默认返回 null原始类型/原始类型包装类 默认值,或者 空集合。比如对于 int/Integer 类型,则返回 0,对于 boolean/Boolean 则返回 false
  • 行为配置(stub)是可以被复写的:比如通常的对象行为是具有一定的配置,但是测试方法可以复写这个行为。请谨记行为复写可能表明潜在的行为太多了。
  • 一旦配置了行为,方法总是会返回 配置值,无论该方法被调用了多少次。
  • 最后一次行为配置是更加重要的,当你为一个带有相同参数的相同方法配置了很多次,最后一次起作用。

参数匹配

Mockito 通过参数对象的 equals() 方法来验证参数是否一致,当需要更多的灵活性时,可以使用参数匹配器:

// Stubbing using built-in anyInt() argument matcher
when(mockedList.get(anyInt())).thenReturn("element");
// Stubbing using custom matcher (let's say isValid() returns your own matcher implementation):
when(mockedList.contains(argThat(isValid()))).thenReturn("element");
// Following prints "element"
System.out.println(mockedList.get(999));
// You can also verify using an argument matcher
verify(mockedList).get(anyInt());
// Argument matchers can also be written as Java 8 Lambdas
verify(mockedList).add(argThat(someString -> someString.length() > 5));

参数匹配器 允许更加灵活的 验证 和 行为配置。更多 内置匹配器 和 自定义参数匹配器 例子请参考:ArgumentMatchersMockitoHamcrest

注意:如果使用了参数匹配器,那么所有的参数都需要提供一个参数匹配器。
verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));
// Above is correct - eq() is also an argument matcher
verify(mock).someMethod(anyInt(), anyString(), "third argument");
// Above is incorrect - exception will be thrown because third argument is given without an argument matcher.

类似 anyObject()eq() 这类匹配器并不返回匹配数值。他们内部记录一个 匹配器堆栈 并返回一个空值(通常为 null)。这个实现是为了匹配 java 编译器的 静态类型安全,这样做的后果就是你不能在 检验/配置方法 外使用 anyObject()eq() 等方法。

校验次数

LinkedList mockedList = mock(LinkedList.class);
// Use mock
mockedList.add("once");
mockedList.add("twice");
mockedList.add("twice");
mockedList.add("three times");
mockedList.add("three times");
mockedList.add("three times");

// Follow two verifications work exactly the same - times(1) is used by default
verify(mockedList).add("once");
verify(mockedList, times(1)).add("once");

// Exact number of invocations verification
verify(mockedList, times(2)).add("twice");
verify(mockedList, times(3)).add("three times");

// Verification using never(). never() is an alias to times(0)
verify(mockedList, never()).add("never happened");

// Verification using atLeast()/atMost()
verify(mockedList, atLeastOnce()).add("three times");
verify(mockedList, atLeast(2)).add("three times");
verify(mockedList, atMost(5)).add("three times");

校验次数方法常用的有如下几个:

|Method |Meaning| |:------|:-------| |times(n)| 次数为n,默认为1(times(1))| |never()| 次数为0,相当于times(0)| |atLeast(n)|最少n次| |atLeastOnce()| 最少一次| |atMost(n)| 最多n次 |

抛出异常

doThrow(new RuntimeException()).when(mockedList).clear();
// following throws RuntimeException
mockedList.clear();

按顺序校验

有时对于一些行为,有先后顺序之分,所以,当我们在校验时,就需要考虑这个行为的先后顺序:

// A. Single mock whose methods must be invoked in a particular order
List singleMock = mock(List.class);
// Use a single mock
singleMock.add("was added first");
singleMock.add("was added second");
// Create an inOrder verifier for a single mock
InOrder inOrder = inOrder(singleMock);
// Following will make sure that add is first called with "was added first, then with "was added second"
inOrder.verify(singleMock).add("was added first");
inOrder.verify(singleMock).add("was added second");

// B. Multiple mocks that must be used in a particular order
List firstMock = mock(List.class);
List secondMock = mock(List.class);
// Use mocks
firstMock.add("was called first");
secondMock.add("was called second");
// Create inOrder object passing any mocks that need to be verified in order
InOrder inOrder = inOrder(firstMock, secondMock);
// Following will make sure that firstMock was called before secondMock
inOrder.verify(firstMock).add("was called first");
inOrder.verify(secondMock).add("was called second");

存根连续调用

对于同一个方法,如果我们想让其在 多次调用 中分别 返回不同 的数值,那么就可以使用存根连续调用:

when(mock.someMethod("some arg"))
    .thenThrow(new RuntimeException())
    .thenReturn("foo");

// First call: throws runtime exception:
mock.someMethod("some arg");
// Second call: prints "foo"
System.out.println(mock.someMethod("some arg"));
// Any consecutive call: prints "foo" as well (last stubbing wins).
System.out.println(mock.someMethod("some arg"));

也可以使用下面更简洁的存根连续调用方法:

when(mock.someMethod("some arg")).thenReturn("one", "two", "three");
注意:存根连续调用要求必须使用链式调用,如果使用的是同个方法的多个存根配置,那么只有最后一个起作用(覆盖前面的存根配置)。
// All mock.someMethod("some arg") calls will return "two"
when(mock.someMethod("some arg").thenReturn("one")
when(mock.someMethod("some arg").thenReturn("two")

无返回值函数

对于 返回类型 为 void 的方法,存根要求使用另一种形式的 when(Object) 函数,因为编译器要求括号内不能存在 void 方法。

例如,存根一个返回类型为 void 的方法,要求调用时抛出一个异常:

doThrow(new RuntimeException()).when(mockedList).clear();
// Following throws RuntimeException:
mockedList.clear();

监视真实对象

前面使用的都是 mock 出来一个对象。这样,当 没有配置/存根 其具体行为的话,结果就会返回 空类型。而如果使用 特务对象spy),那么对于 没有存根 的行为,它会调用 原来对象 的方法。可以把 spy 想象成局部 mock

List list = new LinkedList();
List spy = spy(list);

// Optionally, you can stub out some methods:
when(spy.size()).thenReturn(100);
// Use the spy calls *real* methods
spy.add("one");
spy.add("two");

// Prints "one" - the first element of a list
System.out.println(spy.get(0));
// Size() method was stubbed - 100 is printed
System.out.println(spy.size());
// Optionally, you can verify
verify(spy).add("one");
verify(spy).add("two");
注意:由于 spy 是局部 mock,所以有时候使用 when(Object) 时,无法做到存根作用。此时,就可以考虑使用 doReturn() | Answer() | Throw() 这类方法进行存根:
List list = new LinkedList();
List spy = spy(list);
// Impossible: real method is called so spy.get(0) throws IndexOutOfBoundsException (the list is yet empty)
when(spy.get(0)).thenReturn("foo");
// You have to use doReturn() for stubbing
doReturn("foo").when(spy).get(0);

spy 并不是 真实对象 的 代理。相反的,它对传递过来的 真实对象 进行 克隆。所以,对 真实对象 的任何操作,spy 对象并不会感知到。同理,对 spy 对象的任何操作,也不会影响到 真实对象

当然,如果使用 mock 进行对象的 局部 mock,通过 doCallRealMethod() | thenCallRealMethod() 方法也是可以的:

// You can enable partial mock capabilities selectively on mocks:
Foo mock = mock(Foo.class);
// Be sure the real implementation is 'safe'.
// If real implementation throws exceptions or depends on specific state of the object then you're in trouble.
when(mock.someMethod()).thenCallRealMethod();

测试驱动开发

以 行为驱动开发 的格式使用 //given //when //then 注释为测试用法基石编写测试用例,这正是 Mockito 官方编写测试用例方法,强烈建议使用这种方式测试编写。

import static org.mockito.BDDMockito.*;

 Seller seller = mock(Seller.class);
 Shop shop = new Shop(seller);

 public void shouldBuyBread() throws Exception {
     // Given
     given(seller.askForBread()).willReturn(new Bread());
     // When
     Goods goods = shop.buyBread();
     // Then
     assertThat(goods, containBread());
 }

自定义错误校验输出信息

// Will print a custom message on verification failure
verify(mock, description("This will print on failure")).someMethod();
// Will work with any verification mode
verify(mock, times(2).description("someMethod should be called twice")).someMethod();

@InjectMock

构造器,方法,成员变量依赖注入 使用 @InjectMock 注解时,Mockito 会检查 类构造器方法或 成员变量,依据它们的 类型 进行自动 mock

public class InjectMockTest {
    @Mock
    private User user;
    @Mock
    private ArticleDatabase database;
    @InjectMocks
    private ArticleManager manager;
    @Rule
    public MockitoRule mockitoRule = MockitoJUnit.rule();

    @Test
    public void testInjectMock() {
        // Calls addListener with an instance of ArticleListener
        manager.initialize();
        // Validate that addListener was called
        verify(database).addListener(any(ArticleListener.class));
    }

    public static class ArticleManager {
        private User user;
        private ArticleDatabase database;

        public ArticleManager(User user, ArticleDatabase database) {
            super();
            this.user = user;
            this.database = database;
        }

        public void initialize() {
            database.addListener(new ArticleListener());
        }
    }

    public static class User {
    }

    public static class ArticleListener {
    }

    public static class ArticleDatabase {
        public void addListener(ArticleListener listener) {
        }
    }
}

成员变量 manager 类型为 ArticleManager,它的上面标识别了 @InjectMocks。这意味着要 mock 出 managerMockito 需要先自动 mock 出 ArticleManager 所需的 构造参数(即:user 和 database),最终 mock 得到一个 ArticleManager,赋值给 manager

参数捕捉

ArgumentCaptor 允许在 verify 的时候获取 方法参数内容,这使得我们能在 测试过程 中能对 调用方法参数 进行 捕捉 并 测试

@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
@Captor
private ArgumentCaptor<List<String>> captor;
@Test
public void testArgumentCaptor(){
    List<String> asList = Arrays.asList("someElement_test", "someElement");
    final List<String> mockedList = mock(List.class);
    mockedList.addAll(asList);

    verify(mockedList).addAll(captor.capture()); // When verify,you can capture the arguments of the calling method
    final List<String> capturedArgument = captor.getValue();
    assertThat(capturedArgument, hasItem("someElement"));
}

Mocktio的局限

  1. 不能 mock 静态方法;
  2. 不能 mock 构造器;
  3. 不能 mockequals() 和 hashCode() 方法。

最后感谢每一个认真阅读我文章的人,看着粉丝一路的上涨和关注,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走

mock框架,经验分享,软件测试,自动化测试,单元测试,自动化测试,测试工程师,软件测试,Mock

 这些资料,对于从事【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!凡事要趁早,特别是技术行业,一定要提升技术功底。希望对大家有所帮助…….有需要的小伙伴可以点击下方小卡片免费领取文章来源地址https://www.toymoban.com/news/detail-682809.html

到了这里,关于2023单元测试利器Mockito框架详解(超详细~)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 单元测试利器——手把手教你使用Mockito

    作者:京东零售 秦浩然 从你成为开发人员的那一天起,写单元测试终究是你逃不开的宿命!那开发人员为什么不喜欢写单元测试呢?究其原因,无外乎是依赖。依赖其他的服务、依赖运行的环境、等等,各种依赖都成为了我们写单元测试的绊脚石。那现在有个单元测试利器

    2024年02月08日
    浏览(67)
  • SpringBoot单元测试--Mockito+Junit5框架使用

    作为程序员为了提前发现代码bug,优化代码; 通常我们写完某个功能模块代码后都需要写单元测试对代码块进行测试(特别是敏捷开发中);Java项目最常用的单元测试框架即为Junit(目前最新版本为Junit5),SpringBoot本身也整合了该框架。在写单元测试时代码块中的调到第三方接口方

    2024年02月02日
    浏览(47)
  • 【单元测试】Mockito使用详解

    一个单元测试应该有如下特点: 应该是自动化的 应该可以快速运行 每个单元测试不应该依赖其它测试的结果和执行顺序,单元测试框架可以按任意的顺序执行每个测试 每个单元测试不应该依赖数据库,外部文件,或者任何长时间运行的任务。单元测试应该是独立的,不应该

    2024年04月15日
    浏览(87)
  • Mockito单元测试详解

    依赖: SpringBoot默认的Mock框架是Mockito,和junit一样,只需要依赖spring-boot-starter-test就可以了 (1)@RunWith 指定运行环境,例: @RunWith(SpringRunner.class) Junit运行Spring的测试环境 @RunWith(MockitoJUnitRunner.class) Junit运行Mockito的运行环境,不会加载springboot上下文 @SpringBootTest 加载springbo

    2023年04月09日
    浏览(41)
  • Junit5+Mockito单元测试详解

    1.宏观层面:AIR原则 A:Automatic(自动化) 全自动执行,输出结果无需人工检查,而是通过断言验证。 I:Independent(独立性) 分层测试,各层之间不相互依赖。 R:Repeatable(可重复) 可重复执行,不受外部环境( 网络、服务、中间件等)影响。 2.微观层面:BCDE原则 B: Bord

    2024年01月17日
    浏览(48)
  • 针对mockito框架在单元测试中出现Mybatis-Plus链式调用的解决方案

    1、 调用其他service层方法 2、 调用本service层方法 3、其他问题

    2024年01月24日
    浏览(43)
  • Mockito框架下如何优雅的验证调用Mock对象方法的入参

    该文章已同步至个人微信公众号[不能止步],欢迎关注。 在单元测试场景中,一种典型的场景是为了测试某一个类(Component Under Test, 简称CUT)而需要mock其依赖的的类。示例如下: 为了验证CUT业务实现的正确性,通常需要验证传给调用Mock对象的方法的参数的正确性。如果采

    2024年02月09日
    浏览(57)
  • 单元测试与Mockito

    系列文章目录和关于我 最近在新公司第一次上手写代码,写了一个不是很难的业务逻辑代码,但是在我写单元测试的时候,发现自己对单元测试的理解的就是一坨,整个过程写得慢,还写得臭。造成这种局面我认为是因为: 对Mockito api是不是很熟悉 没有自己单元测试方法论

    2023年04月22日
    浏览(41)
  • Mockito单元测试基本使用

    本文参考: 【码农教程】手把手教你Mockito的使用 - 掘金 (juejin.cn) java - doReturn().when()与when().thenReturn() - 成长之路 - SegmentFault 思否 单元测试实践篇:Mock_阿里巴巴淘系技术团队官网博客的博客-CSDN博客 阿里是如何进行单元测试培训的?_Hollis Chuang的博客-CSDN博客 【Mockito】Mock

    2024年02月10日
    浏览(50)
  • Mockito单元测试异常情况

    一、空指针异常 例子: 使用Collectors.groupingBy()进行分组时, 分组值存在null值 。 解决办法:分组值为null时,默认值为空字符,或者让数据不为空。 二、多个参数匹配异常 例如: 如果a和b一个是正常传参(list、map、string…)另一个是任意万能传参(any()、anyList()、anyString(

    2024年02月07日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包