Android单元测试系列-Mock之Mockito

Posted Chris_166

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android单元测试系列-Mock之Mockito相关的知识,希望对你有一定的参考价值。

一、官网

Mockito:

https://github.com/mockito/mockito

Mockito (Mockito 4.4.0 API)

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

二、Demo示例

参考android Developer中提到的示例来说明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() 
    

// 测试结果及覆盖

三、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 testSubtractExact() 
        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
    

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

大致可以分为两大类: 打桩方法和验证行为。

3. Mockito的局限性

-----------------------------待续--------------------------------

以上是关于Android单元测试系列-Mock之Mockito的主要内容,如果未能解决你的问题,请参考以下文章

Android单元测试系列-Mock之Mockito

Android单元测试系列-Mock之Mockito

java 如何mock参数

补习系列-springboot 单元测试之道

ASP.NET 系列:单元测试之SmtpClient

单元测试之Mock(Moq)