使用 thenReturn、doReturn设置方法的返回值
thenReturn 用来指定特定函数和参数调用的返回值。thenReturn 中可以指定多个返回值,在调用时返回值依次出现。若调用次数超过返回值的数量,再次调用时返回最后一个返回值。
代码语言:javascript复制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 中可以指定多个异常,在调用时异常依次出现。若调用次数超过异常的数量,再次调用时抛出最后一个异常。
代码语言:javascript复制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的函数抛出异常。
代码语言:javascript复制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 接口的对象,在改对象中可以获取调用参数,自定义返回值。
代码语言:javascript复制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 对象。
代码语言:javascript复制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 方法,可以重置之前自定义的返回值和异常。
代码语言:javascript复制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 对象的特定方法特定参数调用。
代码语言:javascript复制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 对象。
代码语言:javascript复制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 对象在不同的单测中会被隔离开。
代码语言:javascript复制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 中配置以下依赖:
代码语言:javascript复制<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 等注解。
代码语言:javascript复制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 反射库 是一个很好用的反射库。本文用它进行临时替换。
代码语言:javascript复制<dependency>
<groupId>org.jooq</groupId>
<artifactId>joor-java-8</artifactId>
<version>0.9.7</version>
</dependency>
代码语言:javascript复制public class HttpService {
public int queryStatus() {
// 发起网络请求,提取返回结果
// 这里直接返回0
return 0;
}
}
代码语言:javascript复制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 "未知状态";
}
}
}
代码语言:javascript复制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() ); // 输出'你好'
}
}