Android单元测试系列(3)-Mock之Mockito

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

目录

一、官网

二、Demo示例

1. 目录结构

2. 被测试的类

3. 测试类

三、Mockito方法说明

1. mock对象创建

2. Mockito框架中的常见方法说明

2.1 常见的打桩方法

2.2 常见的验证行为

2.3 其他方法 

3. Mockito的局限性


一、官网

Mockito:

https://github.com/mockito/mockito

Mockito (Mockito 4.4.0 API)

为什么要用mock:解决测试类对其他类的依赖。在实际的测试过程中,有些需要被测试的方法对其他类对象或变量有依赖,如果不初始化的话,很容易出现NP导致无法顺利的继续测试,这个时候就需要mock这些对象来解决了。

二、Demo示例

参考Android Developer中提到的示例来说明Mockito用法。

1. 目录结构

Android单元测试系列(3)-Mock之Mockito

2. 被测试的类

// gradle引入

dependencies {

    // test目录
    testImplementation 'org.mockito:mockito-core:4.4.0'

    // androidTest目录
    //androidTestImplementation "org.mockito:mockito-android:4.4.0"

}

// 被测试类为 SharedPreferencesHelper

package com.fanff.unittestdemo.mockdemo;

import android.content.SharedPreferences;
import java.util.Calendar;

/**
 * 参考Google官网示例:https://github.com/android/testing-samples/tree/master/unit/BasicSample
 */
public class SharedPreferencesHelper {
    // Keys for saving values in SharedPreferences.
    static final String KEY_NAME = "key_name";
    static final String KEY_DOB = "key_dob_millis";
    static final String KEY_EMAIL = "key_email";

    private final SharedPreferences mSharedPreferences;

    public SharedPreferencesHelper(SharedPreferences sharedPreferences) {
        mSharedPreferences = sharedPreferences;
    }

    public boolean savePersonalInfo(SharedPreferenceEntry sharedPreferenceEntry){
        SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.putString(KEY_NAME, sharedPreferenceEntry.getName());
        editor.putLong(KEY_DOB, sharedPreferenceEntry.getDateOfBirth().getTimeInMillis());
        editor.putString(KEY_EMAIL, sharedPreferenceEntry.getEmail());

        // Commit changes to SharedPreferences.
        return editor.commit();
    }

    public SharedPreferenceEntry getPersonalInfo() {
        // Get data from the SharedPreferences.
        String name = mSharedPreferences.getString(KEY_NAME, "");
        Long dobMillis =
                mSharedPreferences.getLong(KEY_DOB, Calendar.getInstance().getTimeInMillis());
        Calendar dateOfBirth = Calendar.getInstance();
        dateOfBirth.setTimeInMillis(dobMillis);
        String email = mSharedPreferences.getString(KEY_EMAIL, "");

        // Create and fill a SharedPreferenceEntry model object.
        return new SharedPreferenceEntry(name, dateOfBirth, email);
    }
}
package com.fanff.unittestdemo.mockdemo;

import java.util.Calendar;

public class SharedPreferenceEntry {
    // Name of the user.
    private final String mName;

    // Date of Birth of the user.
    private final Calendar mDateOfBirth;

    // Email address of the user.
    private final String mEmail;

    public SharedPreferenceEntry(String name, Calendar dateOfBirth, String email) {
        mName = name;
        mDateOfBirth = dateOfBirth;
        mEmail = email;
    }

    public String getName() {
        return mName;
    }

    public Calendar getDateOfBirth() {
        return mDateOfBirth;
    }

    public String getEmail() {
        return mEmail;
    }
}

3. 测试类

Q:只想测试SharedPreferencesHelper#savePersonalInfo()是否有调用Editor#commit()

A:但是savePersonalInfo()这个方法中需要依赖的对象有SharedPreferenceEntry、Editor,想要绕开这些对象的创建就需要用到mock了。

    public boolean savePersonalInfo(SharedPreferenceEntry sharedPreferenceEntry){
        SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.putString(KEY_NAME, sharedPreferenceEntry.getName());
        editor.putLong(KEY_DOB, sharedPreferenceEntry.getDateOfBirth().getTimeInMillis());
        editor.putString(KEY_EMAIL, sharedPreferenceEntry.getEmail());

        // Commit changes to SharedPreferences.
        return editor.commit();
    }

// 单元测试代码 

package com.fanff.unittestdemo.mockdemo;

import android.content.SharedPreferences;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;

import java.util.Calendar;

import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;


public class SharedPreferencesHelperTest {

    @Before
    public void setUp() throws Exception {
    }

    @After
    public void tearDown() throws Exception {
    }

    @Test
    public void testSavePersonalInfo() {
        // Mock SharedPreferences对象作为SharedPreferencesHelper构造方法的参数
        SharedPreferences sharedPreferences = Mockito.mock(SharedPreferences.class);
        SharedPreferencesHelper sharedPreferencesHelper =
                new SharedPreferencesHelper(sharedPreferences);
        // Mock SharedPreferenceEntry对象作为savePersonalInfo方法的参数
        SharedPreferenceEntry sharedPreferenceEntry = Mockito.mock(SharedPreferenceEntry.class);
        
        // Mock SharedPreferences.Editor对象作为savePersonalInfo方法里的局部变量
        SharedPreferences.Editor editor = Mockito.mock(SharedPreferences.Editor.class);
        // Mock savePersonalInfo()方法内部的执行流程
        when(sharedPreferences.edit()).thenReturn(editor);
        when(editor.putString(anyString(), anyString())).thenReturn(editor);
        when(sharedPreferenceEntry.getDateOfBirth()).thenReturn(Calendar.getInstance());
        when(editor.putLong(anyString(), anyLong())).thenReturn(editor);
        
        // 测试savePersonalInfo()是否有调用commit
        sharedPreferencesHelper.savePersonalInfo(sharedPreferenceEntry);
        Mockito.verify(editor).commit();

    }
}

当然为了简化mock对象的初始化,可以这样写:
(1) 单元测试类定义的开头,添加 @RunWith(MockitoJUnitRunner.class) 注释。此注释可告知 Mockito 测试程序运行的框架;
(2) 在对象字段声明前添加 @Mock 注释。

package com.fanff.unittestdemo.mockdemo;

import android.content.SharedPreferences;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;

import java.util.Calendar;

import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class SharedPreferencesHelperTest {
    
    // 如果使用@Mock注解的方式mock对象则需要在类前加 @RunWith(MockitoJUnitRunner.class)
    @Mock
    SharedPreferences sharedPreferences;
    @Mock
    SharedPreferenceEntry sharedPreferenceEntry;
    
    @Test
    public void testSavePersonalInfo() {
        SharedPreferencesHelper sharedPreferencesHelper =
                new SharedPreferencesHelper(sharedPreferences);

        SharedPreferences.Editor editor = Mockito.mock(SharedPreferences.Editor.class);

        when(sharedPreferences.edit()).thenReturn(editor);
        when(editor.putString(anyString(), anyString())).thenReturn(editor);
        when(sharedPreferenceEntry.getDateOfBirth()).thenReturn(Calendar.getInstance());
        when(editor.putLong(anyString(), anyLong())).thenReturn(editor);

        sharedPreferencesHelper.savePersonalInfo(sharedPreferenceEntry);

        Mockito.verify(editor).commit();
    }

    @Test
    public void testGetPersonalInfo() {
    }
}

// 测试结果及覆盖

Android单元测试系列(3)-Mock之Mockito

三、Mockito方法说明

1. mock对象创建

mock的对象可以通过Mockito.mock()和Mockito.spy()两种方法来创建。

Mock:
 *  (1) mock对象调用的所有方法都是空方法。非void方法都将返回默认值,比如返回值为int的方法将返回0,返回值为对象的方法将返回null等,而void方法将什么都不做;
 *  (2) 适用场景:类对外部依赖较多,只关心少数函数的具体实现。

 Spy:
 *  (1) 是正常对象的替身,跟正常对象的使用一样;
 *  (2) 适用场景:类对外部依赖较少,关心大部分函数的具体实现

// 被测代码: Calculator.java
package com.fanff.unittestdemo.junitdemo;

public class Calculator {
    public int addExact(int x, int y) {
        return x + y;
    }

    public int subtractExact(int x, int y) {
        return x - y;
    }

    public int multiplyExact(int x, int y) {
        return x * y;
    }

    // TODO: zero case
    public int intDivide(int x, int y) {
        return x / y;
    }
}



// 测试代码:CalculatorMockTest.java
package com.fanff.unittestdemo.mockdemo;

import com.fanff.unittestdemo.junitdemo.Calculator;

import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;

public class CalculatorMockTest {

    // Mock vs Spy的区别
    @Test
    public void testMultiplyExact() {
        Calculator calculatorMockObj = Mockito.mock(Calculator.class);
        Assert.assertEquals(0, calculatorMockObj.multiplyExact(6, 8));// Pass
        // Assert.assertEquals(48, calculatorMockObj.multiplyExact(6, 8));// Fail

        Calculator calculatorSpyObj = Mockito.spy(Calculator.class);
        Assert.assertEquals(48, calculatorSpyObj.multiplyExact(6, 8));// Pass
        // Assert.assertEquals(0, calculatorSpyObj.multiplyExact(6, 8));// Fail
    }
}

Q: 如果想Mockito.mock()出来的对象能像spy()出来的对象一样执行方法的具体实现,该如何做?如果Mockito.spy()出来的对象能像mock()出来的对象一样不不去关心执行方法的具体实现,该如何实现呢?

A:使用打桩方法doCallRealMethod()和doReturn()。如下示例。

/mock vs spy

    @Test
    public void testSubtractExact() {
        Calculator calculatorMockObj = Mockito.mock(Calculator.class);
        Assert.assertEquals(0, calculatorMockObj.subtractExact(6, 8));// Pass
        // Assert.assertEquals(-2, calculatorMockObj.subtractExact(6, 8));// Fail

        doCallRealMethod().when(calculatorMockObj).subtractExact(anyInt(), anyInt());
        Assert.assertEquals(-2, calculatorMockObj.subtractExact(6, 8));// Pass
    }


    @Test
    public void testAddExact() {
        Calculator calculatorSpyObj = Mockito.spy(Calculator.class);
        Assert.assertEquals(16, calculatorSpyObj.addExact(8, 8));// Pass
        // Assert.assertEquals(0, calculatorSpyObj.addExact(8, 8));// Fail

        doReturn(0).when(calculatorSpyObj).addExact(anyInt(), anyInt());
        Assert.assertEquals(0, calculatorSpyObj.addExact(8, 8));// Pass
    }

2. Mockito框架中的常见方法说明

常用的API可以分为两大类: 打桩方法和验证行为。

2.1 常见的打桩方法

方法名 描述

thenReturn(T value)

设置要返回的值

thenThrow(Throwable... throwables)

设置要抛出的异常

thenAnswer(Answer<?> answer)

对结果进行拦截

doReturn(Object toBeReturned)

提前设置要返回的值

doThrow(Throwable... toBeThrown)

提前设置要抛出的异常

doAnswer(Answer answer)

提前对结果进行拦截

doCallRealMethod()

调用某一个方法的真实实现

doNothing()

设置void方法什么也不做

2.2 常见的验证行为

验证方法
方法名 描述
after(long millis) 在给定的时间后进行验证
timeout(long millis) 验证方法执行是否超时
atLeast(int minNumberOfInvocations) 至少进行n次验证
atMost(int maxNumberOfInvocations) 至多进行n次验证
description(String description) 验证失败时输出的内容
times(int wantedNumberOfInvocations) 验证调用方法的次数
never() 验证交互没有发生,相当于times(0)
only() 验证方法只被调用一次,相当于times(1)
参数匹配
方法名 描述
anyObject() 匹配任何对象
any(Class<T> type) 与anyObject()一样
any() 与anyObject()一样
anyBoolean() 匹配任何boolean和非空Boolean
anyByte() 匹配任何byte和非空Byte
anyCollection() 匹配任何非空Collection
anyDouble() 匹配任何double和非空Double
anyFloat() 匹配任何float和非空Float
anyInt() 匹配任何int和非空Integer
anyList() 匹配任何非空List
anyLong() 匹配任何long和非空Long
anyMap() 匹配任何非空Map
anyString() 匹配任何非空String
contains(String substring) 参数包含给定的substring字符串
argThat(ArgumentMatcher <T> matcher) 创建自定义的参数匹配模式

2.3 其他方法

方法名 方法描述
reset(T … mocks) 重置Mock
spy(Class<T> classToSpy) 实现调用真实对象的实现
inOrder(Object… mocks) 验证执行顺序
@InjectMocks注解 自动将模拟对象注入到被测试对象中

3. Mockito的局限性

​​​​​​​Android单元测试系列(1)-开篇_Chris_166的博客-CSDN博客

在 "二、单元测试工具链"中已经提过了,下篇介绍解决之道 "powermock"的用法文章来源地址https://www.toymoban.com/news/detail-414058.html

到了这里,关于Android单元测试系列(3)-Mock之Mockito的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 单元测试与Mockito

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

    2023年04月22日
    浏览(37)
  • 单元测试Mockito

    Mockito 1.为什么要mock? mock等于是创建一个虚拟对象(不会去跑真实的),在测试环境中替换掉真是对象,从而 验证该对象某些方法的调用情况,调用多少次,参数等 给对象做一个定义,指定返回结果或者特定的动作 2.什么是mock,stub和spy mock: Mock 对象是一种完全由测试控制

    2024年03月18日
    浏览(39)
  • Spring单元测试+Mockito

    一,背景 单元测试基本上是开发逃不过的一个工作内容,虽然往往因为过于无聊,或者过于麻烦,而停止于项目的迭代之中,不了了之了。其实不是开发们懒,而是上头要求的测试覆盖率高,但是又没有好用的工具,导致工作积压,最后只能舍弃掉这部分。 最近发现Spring+

    2024年04月17日
    浏览(41)
  • 单元测试-mockito

    在单元测试中,对于一个类中的方法,常常需要依赖其他类的方法、操作数据dto等对象实例。 方法mock:依赖的方法又可能依赖其他方法,呈现级联的树状结构。 问题:在一些情况下,这个依赖树会很深,其中依赖的一个子方法一旦修改出现问题,如果引起大量的单测不可用

    2024年04月11日
    浏览(68)
  • Mockito单元测试基本使用

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

    2024年02月10日
    浏览(48)
  • 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日
    浏览(38)
  • 【单元测试】Mockito使用详解

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

    2024年04月15日
    浏览(83)
  • Mockito单元测试异常情况

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

    2024年02月07日
    浏览(48)
  • Java单元测试之Mockito

    Mockito是一种Java Mock框架,主要就是用来做Mock测试的,它可以模拟任何Spring管理的Bean、模拟方法的返回值、模拟抛出异常等等,同时也会记录调用这些模拟方法的参数、调用顺序,从而可以校验出这个Mock对象是否有被正确的顺序调用,以及按照期望的参数被调用。 Mockito的特

    2024年02月13日
    浏览(49)
  • 【SpringBoot】mockito+junit 单元测试

    CommonServiceImpl 在方法 getSourceCodeMap() 调用了 ServiceA 的方法 list(QueryBO queryBo) 。 org.mockito.exceptions.base.MockitoException: No tests found in ClientSyncServiceImplTest Is the method annotated with @Test? Is the method public? 解决方案: Test引入 org.junit.Test 不要引入 org.junit.jupiter.api.Test 例如获取当前用户 moc

    2024年02月09日
    浏览(42)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包