目录
一、概述
二、基本校验器
三、基本匹配器
四、空值匹配器
五、字符串匹配器
六、万能匹配器
七、使用示例
一、概述
在测试中,除了需要将某些含有外部依赖的方法替换为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() ?→ 匹配任何值,包括Nullany(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() ?→ 匹配NullnotNull() ?→ 匹配除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
|