目录

一、概述

二、使用泛型定义

三、使用Object类型


一、概述

有时候,我们需要测试泛型方法,其实与普通方法的Mock方法相同,直接在Mock方法上使用相同的泛型参数即可。下面通过案例说明如何使用,主要有两种方式:

  • 第一种方式:使用泛型定义;
  • 第二种方式:使用Object类型;

二、使用泛型定义

【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官方案例,笔者在学习总结时,在代码上面加上了一些自己的解析步骤,方便大家理解。

三、使用Object类型

由于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。