一个单元测试应该有如下特点
为了满足单元测试的隔离性,隔离外部环境,我们需要将外部依赖,通过替身的方式,模拟出一个可以满足我们测试的假对象。
Mockito 就是可以制作假替身,代替外部依赖,模拟我们自定义的返回操作的替身测试框架。
mock替身,很像电影中的替身演员,在某些场景中我们需要替身来处理某些问题。
Mock 需要先设置测试类被 Mockito 管理。有两种方式。
上述两种方法使用其一都可,效果一样。
@Before 注解是 Junit 提供的,在每个@Test测试执行前执行一次。
方式1代码示例:
@RunWith(MockitoJUnitRunner.class)
public class MockDemo {
@Test
public void testMock() {
// 处理测试
}
}
方式2代码示例:
@Before
public void init() {
MockitoAnnotations.initMocks(this);
}
对类构造替身对象有如下几种方式
第二种方式是该测试类中所有单元测试唯一的对象,为了防止每个单元测试 stubbing 的先后顺序对其他单元测试的影响,最好写一个 @After 方法,在该方法中使用 Mockito.reset() 方法清空 stubbing 规则。
@After 注解是 Junit 提供的,在每个@Test测试执行后执行一次。
方法1代码示例:
@RunWith(MockitoJUnitRunner.class)
public class MockDemo {
@Test
public void testMock() {
// 处理测试
ArrayList mock = mock(ArrayList.class);
}
}
方法2代码示例:
@RunWith(MockitoJUnitRunner.class)
public class MockDemo {
@Mock
private List<String> list;
... 具体测试 ...
// @After 每个单元测试方法结束后执行,重置 stubbing
@After
public void destory() {
Mockito.reset(list);
}
}
Mockito 底层是对被Mock的对象使用 cglib 生成一个代理类。
Mock 是对被测试类的依赖对象构造替身操作,还需将替身注入到被测试类。注入可以使用 @InjectMocks 注解标识需要被注入的类。依赖的其他类 就用@Mock 注解标识,Mockito 自动将替身注入到被测试类。
代码示例:
@RunWith(MockitoJUnitRunner.class)
public class MockTest {
@InjectMocks
private OrderService orderService;
@Mock
private OrderMapper orderMapper;
}
通过 Mock 构造出替身对象后,我们还需要设置替身对象的行为。有点类似录制与播放。比方说设置替身对象调用某方法的返回值。
最常用的一种方式,就是设置某种方法根据某个入参的返回值是什么。或者设置抛出什么异常。
设置正常返回代码示例:
@Test
public void testMock() {
// 处理测试
list = mock(ArrayList.class);
// 设置当调用 list.get(0) 的时候,返回 “first value”
when(list.get(0)).thenReturn("first value");
assertThat(list.get(0), equalTo("first value"));
}
设置正常返回方式二代码示例:
@Test
public void testMock() {
// 处理测试
list = mock(ArrayList.class);
// 设置当调用 list.get(0) 的时候,返回 “first value”
Mockito.doReturn("first value").when(list).get(0);
assertThat(list.get(0), equalTo("first value"));
}
设置抛出异常返回代码示例:
@Test
public void testMock() {
// 设置当调用 list.get(0) 的时候,抛出异常
when(list.get(0)).thenThrow(new RuntimeException());
try {
list.get(0);
fail();
} catch (Exception e) {
assertThat(e, instanceOf(RuntimeException.class));
}
}
针对抛出异常的单元测试 Junit 有更优雅的使用方式,ExpectedException。优点就是减少缩进,代码可读性高。
使用方法很简单,具体见如下Junit异常校验代码示例。都是 org.junit 包下。
Junit异常校验代码示例:
@RunWith(MockitoJUnitRunner.class)
public class MockDemo {
@Mock
private List<String> list;
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void testMock() {
thrown.expect(RuntimeException.class);
when(list.get(0)).thenThrow(new RuntimeException());
list.get(0);
}
}
void 方法,没有返回值,我们也可以设置其什么都不做,也可设置抛出异常。
Void方法什么都不做,执行空逻辑
Mockito.doNothing().when(对象).对象的方法
Void方法抛出异常
执行空逻辑代码示例:
@Test
public void testMockVoid() {
// 设置 list 对象 执行 clear 的时候 不执行实际逻辑,执行空逻辑
Mockito.doNothing().when(list).clear();
list.clear();
// 验证 list 对象 对 clear 方法只执行了一次
Mockito.verify(list, times(1)).clear();
}
抛出异常代码示例:
@Test
public void testMockVoid2() {
thrown.expect(RuntimeException.class);
// 设置 list 对象 执行 clear 的时候 不执行实际逻辑,抛出异常
Mockito.doThrow(new RuntimeException()).when(list).clear();
list.clear();
}
有时候我们需要设置每一次调用的返回值,比方说某个方法第一次调用返回和第二次调用返回不一样。只要按顺序,只要在 thenReturn() 方法中 用逗号分隔即可,或者执行多次 thenReturn(),链式调用。
逗号分隔代码示例:
@Test
public void testMockReturnMultiple() {
Mockito.when(list.size()).thenReturn(1, 2, 3, 4);
assertThat(list.size(), equalTo(1));
assertThat(list.size(), equalTo(2));
assertThat(list.size(), equalTo(3));
assertThat(list.size(), equalTo(4));
}
链式调用代码示例:
@Test
public void testMockReturnMultiple2() {
Mockito.when(list.size()).thenReturn(1).thenReturn(2).thenReturn(3).thenReturn(4);
assertThat(list.size(), equalTo(1));
assertThat(list.size(), equalTo(2));
assertThat(list.size(), equalTo(3));
assertThat(list.size(), equalTo(4));
}
我们可以给替身对象的方法替换具体的行为,也就是修改其方法实现。
代码示例:
@Test
public void testMockByAnswer() {
// anyInt 表示任意 int 类型入参,都处理为 answer 中的行为
Mockito.when(list.get(anyInt())).thenAnswer(invocation -> {
Integer index = invocation.getArgumentAt(0, Integer.class);
return String.valueOf(index * 200);
});
assertThat(list.get(1), equalTo("200"));
assertThat(list.get(5), equalTo("1000"));
}
默认mock 的对象的所有方法如果不 stubbing 处理,都是空方法。我们可以指定替身对象依然执行对象本身的实现。
代码示例:
@Test
public void testMockCallRealMethod() {
// anyInt 表示任意 int 类型入参,都处理为 answer 中的行为
ArrayList<Object> arrayList = mock(ArrayList.class);
Mockito.when(arrayList.size()).thenReturn(1).thenCallRealMethod();
assertThat(arrayList.size(), equalTo(1));
assertThat(arrayList.size(), equalTo(0));
}
经过 spy 方法构造的对象,跟 mock 是相反的,spy 构造的方法,如果不设置 stubbing,则依然执行代理的对象的真正方法。使用方式跟 mock 一致。
spying 英文是间谍的意思,说明这不是替身,而是真正对象中一个特殊的对象,大部分方法都跟正常方法一样,只是某些方法有自己的特殊想法。
使用代码示例:
@RunWith(MockitoJUnitRunner.class)
public class SpyDemo {
@Test
public void testSpy() {
// 处理测试
ArrayList mock = Mockito.spy(ArrayList.class);
}
}
通用匹配。就是 any()这种任意的。有的时候我们不关注入参的值,只关心返回值,传进去什么都可以,那我们就可以选择使用 wildcard matchers 。
值得注意的事使用通用匹配的话 需要都用通用匹配函数,如果想指定某个参数是特殊值 用eq() 包装一下,示例如下:
@Test
public void testMock() {
// anyInt 表示任意 int 类型入参,都处理为 answer 中的行为
ArrayList<Object> arrayList = mock(ArrayList.class);
// 如下 直接使用会报错,因为后边有 anyObject() 通用匹配
// Mockito.when(arrayList.set(1, anyObject())).thenReturn("1");
// 改写成 eq(1) 则可正常运行
Mockito.when(arrayList.set(eq(1), anyObject())).thenReturn("1");
assertThat(arrayList.set(1, new Object()), equalTo("1"));
}
Hamcrest Matchers 是更优雅的断言,就是上述例子中的 assertThat() 系列方法,不同于 Junit 直接写的 Assert.assertEquals() 大量静态方法,hamcrest matchers 提供了类似函数式编程的可自定义策略的断言方式,允许通过函数的组合搭建出具有表达性的断言。其表达性,可扩展性更强。
assertThat 本质是提供了一个骨架,其实现是调用 Matcher 接口的方法。所以我们可以通过扩展 Matcher接口来扩展各种断言方法。
三个参数的 assertThat() 方法 第一个参数表示 reason 字符串,可以描述一下这个断言。在断言不通过时,会打印该描述。
assertThat 的源码如下,其本质是策略模式和模板方法模式:
public static <T> void assertThat(T actual, Matcher<T> matcher) {
assertThat("", actual, matcher);
}
public static <T> void assertThat(String reason, T actual,
Matcher<T> matcher) {
if (!matcher.matches(actual)) {
Description description= new StringDescription();
description.appendText(reason);
description.appendText("\nExpected: ");
description.appendDescriptionOf(matcher);
description.appendText("\n got: ");
description.appendValue(actual);
description.appendText("\n");
throw new java.lang.AssertionError(description.toString());
}
}
示例中的类全路径 org.junit.assertThat,org.hamcrest.Matchers
@Test
public void testMatcher() {
int i = 10;
assertThat(i, equalTo(10));
assertThat(i, not(equalTo(20)));
assertThat(i, is(10));
assertThat(i, is(not(20)));
assertThat(i, greaterThan(0));
assertThat(i, lessThan(20));
assertThat(i, either(equalTo(10)).or(equalTo(20)));
assertThat(i, both(equalTo(10)).and(equalTo(10)));
assertThat(i, anyOf(is(10), equalTo(30), equalTo(20)));
assertThat(i, allOf(is(10), equalTo(30), equalTo(20)));
}
Hamcrest Matchers 的扩展性强,如果其默认实现的 matcher 无法满足我们的需要,我们可以自行定义 matcher。方法如下。
代码示例如下:
public class StringEqualTo<T extends String> extends BaseMatcher<T> {
private String value;
public StringEqualTo(String value) {
this.value = value;
}
// 这个是assertThat 的回调方法,模板方法,实际的断言逻辑
@Override
public boolean matches(Object item) {
return StringUtils.equals(value, (String)item);
}
// @Factory 注解标识该静态方法 是 创建 matcher 的工程方法,只是标识,没有别的作用
@Factory
public static <T extends String> StringEqualTo<T> stringEqualTo(T t) {
return new StringEqualTo<>(t);
}
// 该方法控制断言失败时的文本提示信息
@Override
public void describeTo(Description description) {
description.appendText("字符串不匹配");
}
}
使用方法跟其默认实现的一样,示例如下:
@Test
public void testMatcher() {
String testValue = "hello";
assertThat(testValue, StringEqualTo.stringEqualTo("hello"));
}
最近很多小伙伴,让我帮忙找一套 Java 学习资料,于是我翻遍了收藏的 1024G 资料,找到一套华为工程师总结的 Java 笔记,可以说是 Java 程序员必备!
整个资料包内容专注 Java 技术,包括 Spring、Spring Boot/Cloud、Dubbo、JVM、集合、多线程、JPA、MyBatis、MySQL、大数据、Nginx、Git、Docker、GitHub、Servlet、JavaWeb、IDEA、Redis、算法、面试题等几乎覆盖了 Java 基础和进阶的方方面面,非常适合初学者入门和进阶者巩固知识!
据说已经有小伙伴通过这套资料,成功的入职了蚂蚁金服、今日头条等大厂。而且,这些资料不是扫描版的,里面的文字都可以直接复制,非常便于我们学习!
分享在这里~ 免积分直接下载~
https://blog.csdn.net/weixin_42116348?type=download