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_第1张图片

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_第2张图片

三、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 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 matcher) 创建自定义的参数匹配模式

2.3 其他方法

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

3. Mockito的局限性

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

在 "二、单元测试工具链"中已经提过了,下篇介绍解决之道 "powermock"的用法

你可能感兴趣的:(单元测试,android,单元测试)