Mocktio 使用(下)

Java
475
0
0
2022-10-14

使用 thenReturn、doReturn设置方法的返回值

thenReturn 用来指定特定函数和参数调用的返回值。thenReturn 中可以指定多个返回值,在调用时返回值依次出现。若调用次数超过返回值的数量,再次调用时返回最后一个返回值。

import org.junit.Assert;
import org.junit.Test;
import static org.mockito.Mockito.*;
import org.mockito.MockitoAnnotations;

import java.util.Random;

public class MockitoTest7 {

    @Test 
    public void test() {

        Random mockRandom = mock(Random.class);

        when(mockRandom.nextInt()).thenReturn(1);
        Assert.assertEquals(1, mockRandom.nextInt());
    }

    @Test 
    public void test2() {
        Random mockRandom = mock(Random.class);

        when(mockRandom.nextInt()).thenReturn(1, 2, 3);

        Assert.assertEquals(1, mockRandom.nextInt());
        Assert.assertEquals(2, mockRandom.nextInt());
        Assert.assertEquals(3, mockRandom.nextInt());
        Assert.assertEquals(3, mockRandom.nextInt());
        Assert.assertEquals(3, mockRandom.nextInt());
    }

    @Test 
    public void test3() {

        MockitoAnnotations.initMocks(this);

        Random random = mock(Random.class);
        doReturn(1).when(random).nextInt();

        Assert.assertEquals(1, random.nextInt());
    }
}
doReturn 的作用和thenReturn相同,但使用方式不同:

使用 thenThrow、doThrow让方法抛出异常

thenThrow 用来让函数调用抛出异常。thenThrow 中可以指定多个异常,在调用时异常依次出现。若调用次数超过异常的数量,再次调用时抛出最后一个异常。

import org.junit.Assert;
import org.junit.Test;
import static org.mockito.Mockito.*;

import java.util.Random;

public class MockitoTest8 {

    @Test 
    public void test() {

        Random mockRandom = mock(Random.class);

        when(mockRandom.nextInt()).thenThrow(new RuntimeException("异常"));

        try {
            mockRandom.nextInt();
            Assert.fail();  // 上面会抛出异常,所以不会走到这里
        } catch (Exception ex) {
            System.out.println("RuntimeException");
            Assert.assertTrue(ex instanceof RuntimeException);
            Assert.assertEquals("异常", ex.getMessage());
        }
    }

    @Test 
    public void test2() {

        Random mockRandom = mock(Random.class);

        when(mockRandom.nextInt()).thenThrow(new RuntimeException("异常1"), new RuntimeException("异常2"));

        try {
            mockRandom.nextInt();
            Assert.fail();
        } catch (Exception ex) {
            System.out.println("RuntimeException 1");
            Assert.assertTrue(ex instanceof RuntimeException);
            Assert.assertEquals("异常1", ex.getMessage());
        }

        try {
            mockRandom.nextInt();
            Assert.fail();
        } catch (Exception ex) {
            System.out.println("RuntimeException 2");
            Assert.assertTrue(ex instanceof RuntimeException);
            Assert.assertEquals("异常2", ex.getMessage());
        }
    }
}

如果一个对象的方法的返回值是 void,那么不能用 when .. thenThrow 让该方法抛出异常。用 doThrow 可以让返回void的函数抛出异常。

public class MockitoTest10 {

    static class ExampleService {

        public void hello() {
            System.out.println("Hello");
        }
    }

    @Mock
    private ExampleService exampleService;

    @Test
    public void test() {

        MockitoAnnotations.initMocks(this);

        // 这种写法可以达到效果
        doThrow(new RuntimeException("异常")).when(exampleService).hello();

        try {
            exampleService.hello();
            Assert.fail();
        } catch (RuntimeException ex) {
            Assert.assertEquals("异常", ex.getMessage());
        }
    }

    @Test
    public void test2() {

        MockitoAnnotations.initMocks(this);

        Random random = mock(Random.class);

        // 下面这句等同于 when(random.nextInt()).thenThrow(new RuntimeException("异常"));
        doThrow(new RuntimeException("异常")).when(random).nextInt();

        try {
            random.nextInt();
            Assert.fail();
        } catch (RuntimeException ex) {
            Assert.assertEquals("异常", ex.getMessage());
        }
    }
}

使用then、thenAnswer、doAnswer自定义方法处理逻辑

then 和 thenAnswer 的效果是一样的。它们的参数是实现 Answer 接口的对象,在改对象中可以获取调用参数,自定义返回值。

import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.when;

import static org.mockito.Mockito.*;

public class MockitoTest9 {

    static class ExampleService {

        public int add(int a, int b) {
            return a + b;
        }
    }

    @Mock 
    private ExampleService exampleService;

    @Test 
    public void test() {

        MockitoAnnotations.initMocks(this);

        when(exampleService.add(anyInt(),anyInt())).thenAnswer(new Answer<Integer>() {
            @Override 
            public Integer answer(InvocationOnMock invocation) throws Throwable {
                Object[] args = invocation.getArguments();
                // 获取参数 
                Integer a = (Integer) args[0];
                Integer b = (Integer) args[1];

                // 根据第1个参数,返回不同的值 
                if (a == 1) {
                    return 9;
                }
                if (a == 2) {
                    return 99;
                }
                if (a == 3) {
                    throw new RuntimeException("异常");
                }
                return 999;
            }
        });

        Assert.assertEquals(9, exampleService.add(1, 100));
        Assert.assertEquals(99, exampleService.add(2, 100));

        try {
            exampleService.add(3, 100);
            Assert.fail();
        } catch (RuntimeException ex) {
            Assert.assertEquals("异常", ex.getMessage());
        }
    }

    // doAnswer 的作用和 thenAnswer 相同,但使用方式不同: 
    @Test 
    public void test2() {

        MockitoAnnotations.initMocks(this);

        Random random = mock(Random.class);
        doAnswer(new Answer() {
            @Override 
            public Object answer(InvocationOnMock invocation) throws Throwable {
                return 1;
            }
        }).when(random).nextInt();

        Assert.assertEquals(1, random.nextInt());
    }
}

使用 doNothing 让 void 函数什么都不做

doNothing 用于让 void 函数什么都不做。因为 mock 对象中,void 函数就是什么都不做,所以该方法更适合 spy 对象。

import static org.mockito.Mockito.*;

import org.junit.Test;

public class MockitoTest11 {

    static class ExampleService11 {

        public void hello() {
            System.out.println("Hello");
        }
    }

    @Test 
    public void test() {

        ExampleService11 exampleService = spy(new ExampleService11());
        exampleService.hello();  // 会输出 Hello

        // 让 hello 什么都不做 
        doNothing().when(exampleService).hello();
        exampleService.hello(); // 什么都不输出
    }
}

使用 reset 重置对象

使用 reset 方法,可以重置之前自定义的返回值和异常。

import org.junit.Test;

import org.junit.Assert;

import static org.mockito.Mockito.*;

public class MockitoTest12 {

    static class ExampleService {

        public int add(int a, int b) {
            return a + b;
        }
    }

    @Test
    public void test() {

        ExampleService exampleService = mock(ExampleService.class);

        // mock 对象方法的默认返回值是返回类型的默认值
        Assert.assertEquals(0, exampleService.add(1, 2));

        // 设置让 add(1,2) 返回 100 
        when(exampleService.add(1, 2)).thenReturn(100);
        Assert.assertEquals(100, exampleService.add(1, 2));

        // 重置 mock 对象,add(1,2) 返回 0
        reset(exampleService);
        Assert.assertEquals(0, exampleService.add(1, 2));
    }

    @Test
    public void test2() {

        ExampleService exampleService = spy(new ExampleService());

        // spy 对象方法调用会用真实方法,所以这里返回 3
        Assert.assertEquals(3, exampleService.add(1, 2));

        // 设置让 add(1,2) 返回 100 
        when(exampleService.add(1, 2)).thenReturn(100);
        Assert.assertEquals(100, exampleService.add(1, 2));

        // 重置 spy 对象,add(1,2) 返回 3
        reset(exampleService);
        Assert.assertEquals(3, exampleService.add(1, 2));
    }
}

使用 thenCallRealMethod 调用 spy 真实方法

thenCallRealMethod 可以用来重置 spy 对象的特定方法特定参数调用。

import org.junit.Assert;
import org.junit.Test;

import static org.mockito.Mockito.*;

/**
 */
public class MockitoTest13 {

    static class ExampleService {

        public int add(int a, int b) {
            return a+b;
        }
    }

    @Test
    public void test() {

        ExampleService exampleService = spy(new ExampleService());

        // spy 对象方法调用会用真实方法,所以这里返回 3
        Assert.assertEquals(3, exampleService.add(1, 2));

        // 设置让 add(1,2) 返回 100 
        when(exampleService.add(1, 2)).thenReturn(100);
        when(exampleService.add(2, 2)).thenReturn(100);
        Assert.assertEquals(100, exampleService.add(1, 2));
        Assert.assertEquals(100, exampleService.add(2, 2));

        // 重置 spy 对象,让 add(1,2) 调用真实方法,返回 3 
        when(exampleService.add(1, 2)).thenCallRealMethod();
        Assert.assertEquals(3, exampleService.add(1, 2));

        // add(2, 2) 还是返回 100
        Assert.assertEquals(100, exampleService.add(2, 2));
    }
}

使用 verify 校验是否发生过某些操作

  • 使用 verify 可以校验 mock 对象是否发生过某些操作;
  • 使用 verify 配合 time 方法,可以校验某些操作发生的次数;
import org.junit.Test;

import static org.mockito.Mockito.*;

public class MockitoTest14 {

    static class ExampleService {

        public int add(int a, int b) {
            return a + b;
        }
    }

    @Test
    public void test() {

        ExampleService exampleService = mock(ExampleService.class);

        // 设置让 add(1,2) 返回 100 
        when(exampleService.add(1, 2)).thenReturn(100);

        exampleService.add(1, 2);

        // 校验是否调用过 add(1, 2) -> 校验通过
        verify(exampleService).add(1, 2);

        // 校验是否调用过 add(2, 2) -> 校验不通过
        verify(exampleService).add(2, 2);
    }

    @Test
    public void test2() {

        ExampleService exampleService = mock(ExampleService.class);

        // 第1次调用
        exampleService.add(1, 2);

        // 校验是否调用过一次 add(1, 2) -> 校验通过
        verify(exampleService, times(1)).add(1, 2);

        // 第2次调用
        exampleService.add(1, 2);

        // 校验是否调用过两次 add(1, 2) -> 校验通过
        verify(exampleService, times(2)).add(1, 2);
    }
}

使用 mockingDetails 判断是否为 mock对象、spy对象

Mockito 的 mockingDetails 方法会返回 MockingDetails 对象,它的 isMock 方法可以判断对象是否为 mock 对象,isSpy 方法可以判断对象是否为 spy 对象。

public class MockitoTest15 {

    static class ExampleService {

        public int add(int a, int b) {
            return a+b;
        }
    }

    @Test
    public void test() {

        ExampleService exampleService = mock(ExampleService.class);

        // 判断 exampleService 是否为 mock 对象
        System.out.println( mockingDetails(exampleService).isMock() );     // true

        // 判断 exampleService 是否为 spy 对象
        System.out.println( mockingDetails(exampleService).isSpy() );      // false
    }
}

Mockito 测试隔离

使用 @Mock 修饰的 mock 对象在不同的单测中会被隔离开。

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class MockitoTest16 {

    static class ExampleService {

        public int add(int a, int b) {
            return a + b;
        }
    }

    @Mock
    private ExampleService exampleService;

    @Test
    public void test01() {
        System.out.println("---call test01---");

        System.out.println("打桩前: " + exampleService.add(1, 2));

        when(exampleService.add(1, 2)).thenReturn(100);

        System.out.println("打桩后: " + exampleService.add(1, 2));
    }

    @Test
    public void test02() {
        System.out.println("---call test02---");

        System.out.println("打桩前: " + exampleService.add(1, 2));

        when(exampleService.add(1, 2)).thenReturn(100);

        System.out.println("打桩后: " + exampleService.add(1, 2));
    }
}

执行结果:
---call test01---
打桩前: 0
打桩后: 100
---call test02---
打桩前: 0
打桩后: 100

test01 先被执行,打桩前调用add(1, 2)的结果是0,打桩后是 100。然后 test02 被执行,打桩前调用add(1, 2)的结果是0,而非 100,这证明了我们上面的说法。

使用 PowerMock 让 Mockito 支持静态方法

PowerMock 是一个增强库,用来增加 Mockito 、EasyMock 等测试库的功能。

Mockito 默认是不支持静态方法,比如我们在 ExampleService 类中定义静态方法 add,尝试给静态方法打桩,会报错。

可以用 Powermock 弥补 Mockito 缺失的静态方法 mock 功能。在 pom 中配置以下依赖:

<dependency>
	<groupId>org.mockito</groupId>
	<artifactId>mockito-core</artifactId>
	<version>2.25.1</version>
</dependency>
<dependency>
	<groupId>org.powermock</groupId>
	<artifactId>powermock-core</artifactId>
	<version>2.0.0</version>
</dependency>
<dependency>
	<groupId>org.powermock</groupId>
	<artifactId>powermock-module-junit4</artifactId>
	<version>2.0.0</version>
</dependency>
<dependency>
	<groupId>org.powermock</groupId>
	<artifactId>powermock-api-mockito2</artifactId>
	<version>2.0.0</version>
</dependency>

PowerMockRunner 支持 Mockito 的 @Mock 等注解。

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import java.util.Random;

import static org.mockito.Mockito.*;

/**
 * @author Huan Lee
 * @version 1.0
 * @date 10/13/21 4:42 PM
 * @describtion 业精于勤,荒于嬉;行成于思,毁于随。
 */
@RunWith(PowerMockRunner.class)     // 这是必须的
@PrepareForTest(ExampleService17.class)  // 声明要处理 ExampleService17
public class MockitoTest17 {

    @Mock 
    private Random random;

    @Test 
    public void test() {

        PowerMockito.mockStatic(ExampleService17.class);  // 这也是必须的

        when(ExampleService17.add(1, 2)).thenReturn(100);

        Assert.assertEquals(100, ExampleService17.add(1, 2));
        Assert.assertEquals(0, ExampleService17.add(2, 2));
    }

    @Test 
    public void test2() {

        when(random.nextInt()).thenReturn(1);
        Assert.assertEquals(1,  random.nextInt());
    }
}

class ExampleService17 {

    public static int add(int a, int b) {
        return a + b;
    }
}

临时 mock 对象

如果需要临时将一个对象的内部对象替换为 mock 对象,在无法通过set和get处理内部对象的情况下,可以利用反射搞定。

Java JOOR 反射库 是一个很好用的反射库。本文用它进行临时替换。

<dependency>
	<groupId>org.jooq</groupId>
	<artifactId>joor-java-8</artifactId>
	<version>0.9.7</version>
</dependency>
public class HttpService {

    public int queryStatus() {
        // 发起网络请求,提取返回结果 
        // 这里直接返回0 
        return 0;
    }
}
public class BizService {

    private HttpService httpService = new HttpService();

    public String hello() {
        int status = httpService.queryStatus();
        if (status == 0) {
            return "你好";
        }
        else if (status == 1) {
            return "Hello";
        }
        else {
            return "未知状态";
        }
    }
}
import org.joor.Reflect;
import org.junit.Test;

import static org.mockito.Mockito.*;

public class BizServiceTest {

    private BizService bizService = new BizService();

    @Test 
    public void testHello() {

        System.out.println( bizService.hello() );  // 输出'你好'

        // 取出原有的对象 
        Object realHttpService = Reflect.on(bizService).get("httpService");

        // 创建 mock 对象,并用它替换掉 bizService 中的 httpService 对象 
        HttpService mockHttpService = mock(HttpService.class);
        when(mockHttpService.queryStatus()).thenReturn(1);
        Reflect.on(bizService).set("httpService", mockHttpService);

        System.out.println( bizService.hello() );  // 输出'hello'

        // 再将原先的对象设置回去 
        Reflect.on(bizService).set("httpService", realHttpService);
        System.out.println( bizService.hello() );  // 输出'你好'
    }
}