当前位置: 首页 > 工具软件 > TestableMock > 使用案例 >

单元测试框架TestableMock快速入门(三):校验Mock调用

杨甫
2023-12-01

目录

一、概述

二、基本校验器

三、基本匹配器

四、空值匹配器

五、字符串匹配器

六、万能匹配器

七、使用示例


一、概述

在测试中,除了需要将某些含有外部依赖的方法替换为Mock,经常还会需要验证该方法被调用时的参数是否符合预期。

TestableMock中提供了校验器(verifier)和匹配器(matcher)来实现这一功能。譬如:

@Test
public test_case() {
    int res = insToTest.methodToTest();
    verify("mockMethod").with(123, "abc");
}

这个用例会检查在执行被测方法methodToTest()时,名称是mockMethod的Mock方法是否有被调用过,且调用时收到的参数值是否为123"abc"(假设被Mock的mockMethod方法有两个参数)。

除了这种简单校验以外,TestableMock当前已经支持了多种校验器,以及能够模糊匹配参数特征的匹配器

二、基本校验器

  • with(Object... args) → 验证方法是否被指定参数调用过;
  • withInOrder(Object... args) → 如果指定方法被调用了多次,依据实际调用顺序依次匹配;
  • withTimes(int expectedCount) → 验证方法是否被调用过指定次数,忽略对调用参数的检查;
  • without(Object... args) → 验证方法从未被使用指定参数调用过;
  • times(int count) → 连在with()withInOrder()方法之后使用,验证该方法被同样条件的参数调用过了指定次数;

三、基本匹配器

  • any() → 匹配任何值,包括Null
  • any(Class<?> clazz) → 匹配任何指定类型或子类型的值
  • anyTypeOf(Class<?>... classes) → 匹配在列表中的任意一种类型的值
  • anyString() → 匹配任何字符串
  • anyNumber() → 匹配任何数值(整数或浮点数)
  • anyBoolean() → 匹配任何布尔值
  • anyByte() → 匹配任何单字节类型的值
  • anyChar() → 匹配任何单字符类型的值
  • anyInt() → 匹配任何整数类型的值
  • anyLong() → 匹配任何长整数类型的值
  • anyFloat() → 匹配任何浮点数类型的值
  • anyDouble() → 匹配任何双精度浮点数类型的值
  • anyShort() → 匹配任何短整数类型的值
  • anyArray() → 匹配任何数组
  • anyArrayOf(Class<?> clazz) → 匹配任何指定类型的数组
  • anyList() → 匹配任何列表
  • anyListOf(Class<?> clazz) → 匹配任何指定类型的列表
  • anySet() → 匹配任何集合
  • anySetOf(Class<?> clazz) → 匹配任何指定类型的集合
  • anyMap() → 匹配任何映射
  • anyMapOf(Class<?> keyClass, Class<?> valueClass) → 匹配任何指定类型的映射
  • anyCollection() → 匹配任何容器
  • anyCollectionOf(Class<?> clazz) → 匹配任何指定类型的容器
  • anyIterable() → 匹配任何迭代器
  • anyIterableOf(Class<?> clazz) → 匹配任何指定类型的迭代器
  • eq(Object obj) → 匹配与指定值相等的对象
  • refEq(Object obj) → 匹配指定对象(非值相等,而是就是同一个对象)

四、空值匹配器

  • isNull() → 匹配Null
  • notNull() → 匹配除Null以外的任何值
  • nullable(Class<?> clazz) → 匹配空或指定类型的任何值

五、字符串匹配器

  • contains(String substring) → 匹配包含特定子串的字符串
  • matches(String regex) → 匹配符合指定正则表达式的字符串
  • endsWith(String suffix) → 匹配以指定子串结尾的字符串
  • startsWith(String prefix) → 匹配以指定子串开头的字符串

六、万能匹配器

  • any(MatchFunction matcher) → 匹配符合指定表达式的值

七、使用示例

【a】编写被测试类

package com.wsh.testable.mock.testablemockdemo;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

/**
 * 演示Mock方法调用校验器
 */
public class DemoMatcher {

    /**
     * Method to be mocked
     */
    private void methodToBeMocked() {
        // pretend to have some code here
    }

    /**
     * Method to be mocked
     */
    private void methodToBeMocked(Object a1, Object a2) {
        // pretend to have some code here
    }

    /**
     * Method to be mocked
     */
    private void methodToBeMocked(Object[] a) {
        // pretend to have some code here
    }

    public void callMethodWithoutArgument() {
        methodToBeMocked();
    }

    public void callMethodWithNumberArguments() {
        // named variable and lambda variable will be recorded as different type
        // should have them both in test case
        List<Float> floatList = new ArrayList<>();
        floatList.add(1.0F);
        floatList.add(2.0F);
        Long[] longArray = new Long[]{1L, 2L};
        methodToBeMocked(1, 2);
        methodToBeMocked(1L, 2.0);
        methodToBeMocked(new ArrayList<Integer>(){{ add(1); }}, new HashSet<Float>(){{ add(1.0F); }});
        methodToBeMocked(1.0, new HashMap<Integer, Float>(2){{ put(1, 1.0F); }});
        methodToBeMocked(floatList, floatList);
        methodToBeMocked(longArray);
        methodToBeMocked(new Double[]{1.0, 2.0});
    }

    public void callMethodWithStringArgument() {
        methodToBeMocked("hello", "world");
        methodToBeMocked("testable", "mock");
        methodToBeMocked(new String[]{"demo"});
    }

    public void callMethodWithObjectArgument() {
        methodToBeMocked(new BlackBox("hello"), new BlackBox("world"));
        methodToBeMocked(new BlackBox("demo"), null);
        methodToBeMocked(null, new BlackBox("demo"));
    }

}

【b】编写测试类

package com.wsh.testable.mock.testablemockdemo;

import com.alibaba.testable.core.annotation.MockMethod;
import com.alibaba.testable.core.error.VerifyFailedError;
import org.junit.jupiter.api.Test;

import static com.alibaba.testable.core.matcher.InvokeMatcher.any;
import static com.alibaba.testable.core.matcher.InvokeMatcher.anyArray;
import static com.alibaba.testable.core.matcher.InvokeMatcher.anyArrayOf;
import static com.alibaba.testable.core.matcher.InvokeMatcher.anyInt;
import static com.alibaba.testable.core.matcher.InvokeMatcher.anyList;
import static com.alibaba.testable.core.matcher.InvokeMatcher.anyListOf;
import static com.alibaba.testable.core.matcher.InvokeMatcher.anyLong;
import static com.alibaba.testable.core.matcher.InvokeMatcher.anyMapOf;
import static com.alibaba.testable.core.matcher.InvokeMatcher.anyNumber;
import static com.alibaba.testable.core.matcher.InvokeMatcher.anySetOf;
import static com.alibaba.testable.core.matcher.InvokeMatcher.anyString;
import static com.alibaba.testable.core.matcher.InvokeMatcher.contains;
import static com.alibaba.testable.core.matcher.InvokeMatcher.endsWith;
import static com.alibaba.testable.core.matcher.InvokeMatcher.isNull;
import static com.alibaba.testable.core.matcher.InvokeMatcher.matches;
import static com.alibaba.testable.core.matcher.InvokeMatcher.notNull;
import static com.alibaba.testable.core.matcher.InvokeMatcher.nullable;
import static com.alibaba.testable.core.matcher.InvokeMatcher.startsWith;
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
import static org.junit.jupiter.api.Assertions.fail;

/**
 * 演示Mock方法调用校验器
 */
class DemoMatcherTest {

    /**
     * 被测试类
     */
    private DemoMatcher demoMatcher = new DemoMatcher();

    /**
     * Mock容器
     */
    public static class Mock {
        /**
         * 未指明targetClass,默认重写第一个参数类型里面,即DemoMatcher的methodToBeMocked()方法
         */
        @MockMethod(targetMethod = "methodToBeMocked")
        private void methodWithoutArgument(DemoMatcher self) {
        }

        /**
         * Mock DemoMatcher被测类的methodToBeMocked(Object a1, Object a2)方法
         */
        @MockMethod(targetMethod = "methodToBeMocked")
        private void methodWithArguments(DemoMatcher self, Object a1, Object a2) {
        }

        /**
         * Mock DemoMatcher被测类的methodToBeMocked(Object[] a)方法
         */
        @MockMethod(targetMethod = "methodToBeMocked")
        private void methodWithArrayArgument(DemoMatcher self, Object[] a) {
        }
    }

    /**
     * 1. withTimes(int expectedCount) → 验证方法是否被调用过指定次数,忽略对调用参数的检查
     */
    @Test
    void should_match_no_argument() {
        //callMethodWithoutArgument()方法内部调用了methodToBeMocked()方法
        demoMatcher.callMethodWithoutArgument();
        //检查在执行被测方法callMethodWithoutArgument()时,名称是methodWithoutArgument的Mock方法是否有被调用过一次,并忽略对调用参数的检查
        verify("methodWithoutArgument").withTimes(1);
        demoMatcher.callMethodWithoutArgument();
        //检查在执行被测方法callMethodWithoutArgument()时,名称是methodWithoutArgument的Mock方法是否有被调用过两次,并忽略对调用参数的检查
        verify("methodWithoutArgument").withTimes(2);
    }

    /**
     * 2.with(Object... args) → 验证方法是否被指定参数调用过
     * withInOrder(Object... args) → 如果指定方法被调用了多次,依据实际调用顺序依次匹配
     * without(Object... args) → 验证方法从未被使用指定参数调用过
     */
    @Test
    void should_match_number_arguments() {
        //callMethodWithNumberArguments()方法内部调用了methodToBeMocked()很多重载的方法
        demoMatcher.callMethodWithNumberArguments();
        //验证Mock方法methodWithArguments()从未被使用指定参数调用过
        verify("methodWithArguments").without(anyString(), 2);
        //withInOrder(Object... args) → 如果指定方法被调用了多次,依据实际调用顺序依次匹配
        verify("methodWithArguments").withInOrder(anyInt(), 2);
        verify("methodWithArguments").withInOrder(anyLong(), anyNumber());

        //验证Mock方法methodWithArguments()是否被指定参数调用过
        verify("methodWithArguments").with(1.0, anyMapOf(Integer.class, Float.class));
        verify("methodWithArguments").with(anyList(), anySetOf(Float.class));
        verify("methodWithArguments").with(anyList(), anyListOf(Float.class));

        //验证Mock方法methodWithArrayArgument()是否被指定参数调用过
        verify("methodWithArrayArgument").with(anyArrayOf(Long.class));
        verify("methodWithArrayArgument").with(anyArray());
    }

    /**
     * 3.
     * contains(String substring) → 匹配包含特定子串的字符串
     * matches(String regex) → 匹配符合指定正则表达式的字符串
     * endsWith(String suffix) → 匹配以指定子串结尾的字符串
     * startsWith(String prefix) → 匹配以指定子串开头的字符串
     */
    @Test
    void should_match_string_arguments() {
        //注意callMethodWithStringArgument()在类DemoMatcher中的实现
        demoMatcher.callMethodWithStringArgument();
        //验证Mock方法methodWithArguments()是否被指定参数调用过,参数分别满足以"he"开头、以"ld"结尾
        verify("methodWithArguments").with(startsWith("he"), endsWith("ld"));
        //参数类型:包含"stab"、匹配"m.[cd]k"正则表达式
        verify("methodWithArguments").with(contains("stab"), matches("m.[cd]k"));
        //参数类型:String类型的数组
        verify("methodWithArrayArgument").with(anyArrayOf(String.class));
    }


    /**
     * 4.
     * withInOrder(Object... args) → 如果指定方法被调用了多次,依据实际调用顺序依次匹配
     */
    @Test
    void should_match_object_arguments() {
        demoMatcher.callMethodWithObjectArgument();
        verify("methodWithArguments").withInOrder(any(BlackBox.class), any(BlackBox.class));
        verify("methodWithArguments").withInOrder(nullable(BlackBox.class), nullable(BlackBox.class));
        verify("methodWithArguments").withInOrder(isNull(), notNull());
    }

    /**
     * 5.
     * times(int count) → 连在with()或withInOrder()方法之后使用,验证该方法被同样条件的参数调用过了指定次数
     */
    @Test
    void should_match_with_times() {
        demoMatcher.callMethodWithNumberArguments();
        verify("methodWithArguments").with(anyNumber(), any()).times(3);

        demoMatcher.callMethodWithNumberArguments();
        boolean gotError = false;
        try {
            verify("methodWithArguments").with(anyNumber(), any()).times(4);
        } catch (VerifyFailedError e) {
            gotError = true;
        }
        if (!gotError) {
            fail();
        }
    }

}

本案例来自官网, 笔者只是做一个学习总结。更详细使用介绍参考官网:TestableMock

 类似资料: