目录
有时候,我们需要测试泛型方法,其实与普通方法的Mock方法相同,直接在Mock方法上使用相同的泛型参数即可。下面通过案例说明如何使用,主要有两种方式:
【a】编写被测试类
package com.wsh.testable.mock.testablemockdemo;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 演示模板方法的Mock场景
*/
public class DemoTemplate {
private <T> List<T> getList(T value) {
List<T> l = new ArrayList<>();
l.add(value);
return l;
}
private <K, V> Map<K, V> getMap(K key, V value) {
Map<K, V> m = new HashMap<>();
m.put(key, value);
return m;
}
public String singleTemplateMethod() {
List<String> list = getList("demo");
return list.get(0);
}
public String doubleTemplateMethod() {
Map<String, String> map = getMap("hello", "testable");
return map.get("hello");
}
public Set<?> newTemplateMethod() {
Set<String> set = new HashSet<>();
set.add("world");
return set;
}
}
【b】使用泛型定义编写测试类
package com.wsh.testable.mock.testablemockdemo;
import com.alibaba.testable.core.annotation.MockConstructor;
import com.alibaba.testable.core.annotation.MockMethod;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* 演示模板方法的Mock场景
*/
class DemoTemplateTest {
/**
* 被测类
*/
private DemoTemplate demoTemplate = new DemoTemplate();
public static class Mock {
/* 第一种写法:使用泛型定义 */
@MockMethod
private <T> List<T> getList(DemoTemplate self, T value) {
return new ArrayList<T>() {{
add((T) (value.toString() + "_mock_list"));
}};
}
@MockMethod
private <K, V> Map<K, V> getMap(DemoTemplate self, K key, V value) {
return new HashMap<K, V>() {{
put(key, (V) (value.toString() + "_mock_map"));
}};
}
@MockConstructor
private <T> HashSet<T> newHashSet() {
HashSet<T> set = new HashSet<>();
set.add((T) "insert_mock");
return set;
}
@MockMethod
private <E> boolean add(Set s, E e) {
s.add(e.toString() + "_mocked");
return true;
}
}
/**
* 解析:
* 1. singleTemplateMethod()方法内部调用了getList("demo")
* 2. 在Mock容器中覆写了getList方法: value.toString() + "_mock_list")
* 3. 故返回结果为"demo_mock_list"
*/
@Test
void should_mock_single_template_method() {
String res = demoTemplate.singleTemplateMethod();
assertEquals("demo_mock_list", res);
}
/**
* 解析:
* 1. doubleTemplateMethod()方法内部调用了getMap("hello", "testable");
* 2. Mock容器中覆写了getMap()方法:put(key, (V) (value.toString() + "_mock_map")); ==> {hello = testable_mock_map}
* 3. 所以doubleTemplateMethod() = map.get("hello") ==> 返回testable_mock_map
*/
@Test
void should_mock_double_template_method() {
String res = demoTemplate.doubleTemplateMethod();
assertEquals("testable_mock_map", res);
}
/**
* 解析:
* 1. newTemplateMethod()方法内部调用了new HashSet<>()和set.add("world")
* 2. 在Mock容器中覆写了HashSet的构造方法、Set的add方法
* 3. Mock#newHashSet()方法:set.add((T) "insert_mock"); => "insert_mock"
* Mock#add(Set s, E e)方法:s.add(e.toString() + "_mocked"); => "world_mocked"
* 4. 故Set集合大小为2,两个元素分别为:"insert_mock"、"world_mocked"
*/
@Test
void should_mock_new_template_method() {
Set<?> res = demoTemplate.newTemplateMethod();
assertEquals(2, res.size());
Iterator<?> iterator = res.stream().iterator();
assertEquals("insert_mock", iterator.next());
assertEquals("world_mocked", iterator.next());
}
}
此案例为TestableMock官方案例,笔者在学习总结时,在代码上面加上了一些自己的解析步骤,方便大家理解。
由于JVM存在泛型擦除机制,对于Java项目也可以直接使用Object类型替代泛型参数,如下示例:
package com.wsh.testable.mock.testablemockdemo;
import com.alibaba.testable.core.annotation.MockConstructor;
import com.alibaba.testable.core.annotation.MockMethod;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* 演示模板方法的Mock场景
*/
class DemoTemplateTest {
/**
* 被测类
*/
private DemoTemplate demoTemplate = new DemoTemplate();
public static class Mock {
/* 第二种写法:使用Object类型 */
@MockMethod
private List<Object> getList(DemoTemplate self, Object value) {
return new ArrayList<Object>() {{
add(value.toString() + "_mock_list");
}};
}
@MockMethod
private Map<Object, Object> getMap(DemoTemplate self, Object key, Object value) {
return new HashMap<Object, Object>() {{
put(key, value.toString() + "_mock_map");
}};
}
@MockConstructor
private HashSet newHashSet() {
HashSet<Object> set = new HashSet<>();
set.add("insert_mock");
return set;
}
@MockMethod
private boolean add(Set s, Object e) {
s.add(e.toString() + "_mocked");
return true;
}
}
/**
* 解析:
* 1. singleTemplateMethod()方法内部调用了getList("demo")
* 2. 在Mock容器中覆写了getList方法: value.toString() + "_mock_list")
* 3. 故返回结果为"demo_mock_list"
*/
@Test
void should_mock_single_template_method() {
String res = demoTemplate.singleTemplateMethod();
assertEquals("demo_mock_list", res);
}
/**
* 解析:
* 1. doubleTemplateMethod()方法内部调用了getMap("hello", "testable");
* 2. Mock容器中覆写了getMap()方法:put(key, (V) (value.toString() + "_mock_map")); ==> {hello = testable_mock_map}
* 3. 所以doubleTemplateMethod() = map.get("hello") ==> 返回testable_mock_map
*/
@Test
void should_mock_double_template_method() {
String res = demoTemplate.doubleTemplateMethod();
assertEquals("testable_mock_map", res);
}
/**
* 解析:
* 1. newTemplateMethod()方法内部调用了new HashSet<>()和set.add("world")
* 2. 在Mock容器中覆写了HashSet的构造方法、Set的add方法
* 3. Mock#newHashSet()方法:set.add((T) "insert_mock"); => "insert_mock"
* Mock#add(Set s, E e)方法:s.add(e.toString() + "_mocked"); => "world_mocked"
* 4. 故Set集合大小为2,两个元素分别为:"insert_mock"、"world_mocked"
*/
@Test
void should_mock_new_template_method() {
Set<?> res = demoTemplate.newTemplateMethod();
assertEquals(2, res.size());
Iterator<?> iterator = res.stream().iterator();
assertEquals("insert_mock", iterator.next());
assertEquals("world_mocked", iterator.next());
}
}
上述的单元测试都是正常通过的,我们可以看到,两种方式都可以实现泛型方法的Mock,其实跟普通方法差不多,简单理解就是,将原方法的定义原封不动搬到Mock容器类中,然后使用注解@MockMethod注解,配合两个属性targetClass/targetMethod即可完成对依赖方法的Mock。