最近周杰伦发新歌了. 我来蹭下上一首歌《Mojito》的热度.
歌名mojito,是一种透明无色鸡尾酒的名字。有个低侵入性的Java框架,也取自这酒的谐音: mockito.
mockito简介
mockito 是 Java 测试中常用的 mock 工具,用于对对象和对象上的方法进行 mock,指定针对各种不同参数时候的行为.
mockito 框架引入
引入了spring-boot-starter-test
的项目呢, 都会包含有mockito
, 不用额外引入.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
使用示例
框架 API 简洁,主要入口,都在Mockito.class
上的静态方法里.
假设现有基本类两个:
1. KissService
@Service
public class KissService {
@Autowired
KissDao kissDao;
public Integer sum(Integer a, int b) {
return kissDao.add(a, b);
}
}
2. KissDao
@Repository
public class KissDao {
@Autowired
JdbcTemplate jdbc;
public Integer add(Integer a, int b) {
return jdbc.queryForObject("SELECT ? + ?", new SingleColumnRowMapper<>(), a, b);
}
}
借着这个场景,简单介绍下几种基本使用方式.
Mockito.mock
获取一个假对象
如果要测试service
逻辑, 一般是要隔离开数据层的.这时候就不能用真实的 DAO,而是做一个假的 DAO 接入进去.
获取假KissDao
的方法是Mockito.mock(Class)
, 会直接得到一个假对象.
@Test
void mock(){
var service = new KissService();
service.kissDao = Mockito.mock(KissDao.class);
Assertions.assertNotNull(service.kissDao);
}
实现方式,是生成了一个当前类的代理或子类的对象,具体会涉及到MockMaker
的不同实现:
-
ProxyMockMaker -
SubclassByteBuddyMockMaker -
….
现在大概知道,这种方式,final 类支持不太好就行.如何处理final类,晚点再说.
设定方法返回值
前面的类,已经 mock 过了, 但是方法都是空的,没有具体实现代码. 如果直接调用, 不会报错,但会返回默认值.
@Test
void mockCall() {
var kissDao = Mockito.mock(KissDao.class);
System.out.println(kissDao.add(1, 2));
//0
}
可以通过when()
来设定 mock 对象调用方法时候的返回值.
//入参是固定值1,2时候返回3
when(kissDao.add(1,2)).thenReturn(3);
assertEquals(kissDao.add(1,2),3);
设定某种情况下的返回值
传递给mock方法的参数, 有个术语叫ArgumentMatcher
.matcher
相当于是一个充当filter的callback, 当参数传入时候,会通过matcher
判断条件是否满足.
比如下面这句, 就是判断第一个数大于10,第二个数<3时候, 返回3.
when(kissDao.add(
ArgumentMatchers.argThat(x->x>10),
ArgumentMatchers.intThat(x->x<3)))
.thenReturn(3);
assertEquals(kissDao.add(11, 2), 3);
当然, 不用每次都这么复杂的去写.
ArgumentMatchers
上有多个工厂方法, 可以返回各种各样的预设Matcher
,不够用的话再用自定义的.
比如下面用的都是默认的.
//入参等于1,2时候返回3
when(kissDao.add(eq(1), eq(2))).thenReturn(3);
assertEquals(kissDao.add(1, 2), 3);
//入参是任意整数时候都返回3
when(kissDao.add(anyInt(),anyInt())).thenReturn(3);
assertEquals(kissDao.add(4,5),3);
//入参是任意对象和整数时候都返回3
when(kissDao.add(any(),anyInt())).thenReturn(3);
assertEquals(kissDao.add(5,4),3);
这里有个细微差别
-
any
是针对对象进行过滤,返回对象的默认值null
. -
anyInt
是按照int
过滤,返回的是int默认值:0
.
除此以外, eq,refEq,same
也都有支持.
动态响应
前面thenReturn
返回的是固定值, 这里也可以放一个回调进去,不过要用thenAnswer
接口.
下面写法是调用时候,返回随机数.
when(kissDao.add(any(),anyInt()))
.thenAnswer(c->new Random().nextInt());
assertNotEquals(kissDao.add(5,4),kissDao.add(5,4));
根据参数动态响应
响应也可以针对参数做变化, 这时候可以在回调内部去获取参数.
when(kissDao.add(any(),anyInt()))
.thenAnswer(
c-> c.getArgument(0, Integer.class)
+ c.getArgument(1, Integer.class));
assertEquals(kissDao.add(1,5),6);
主要是使用getArgument
方法, 剩下的就是回调的编写问题.
返回异常
前面都是返回内容,但有时需要抛出异常,要用thenThrows
.
when(kissDao.add(any(),anyInt()))
.thenThrow(NullPointerException.class);
assertThrows(NullPointerException.class,
()->kissDao.add(1,5));
几种特殊情况
学一个mock..when..return
基本能解决 1 半以上测试场景.
不过除了基本的 mock,还有些特殊情况需要处理,下面说一下.
重置函数 reset
写了太多when
以后,可能自己也搞不清楚当前的对象状态. 这时候可以用Mockito.reset
恢复到出厂设置.
半真半假的Spy
mock 行为, 是生成假的子类. when
设置过的方法调用走自定义,其他默认所有方法都是调用假的.
这里的默认行为是可以改的,还有一个常用方法,叫Mockito.spy
,用来生成半真半假的对象.
spy 对象,可以当成对已有对象的装饰, 正好是跟 mock 反过来. 所有方法都调用真实的, 除非设置了假的.
@Test
void spyWhen() {
KissService spy = Mockito.spy(new KissService());
Mockito.doReturn(4)
.when(spy).sum(any(), any());
assertDoesNotThrow(() -> spy.sum(1, 2));
assertEquals(4, spy.sum(1, 2));
}
这就是给spy 做一个假方法的方式:Mockito.doReturn
.
无返回值的方法怎么处理
有时候,我们会遇到很多没有返回值,全靠副作用的方法需要处理.
比如给前面的 DAO 加上下面这个方法:
public void findAllInto(String sql, List<Object> result){
List<Map<String, Object>> maps = jdbc.queryForList(sql);
result.addAll(maps);
}
单纯的
mock.. when.. return
流派是不行的,处理不了,when 是需要参数的,这里会报错.
针对这种场景,最好像前面在spy
部分用的Mockito.doReturn
一样, 这里用Mockito.doAnswer
来处理.
@Test
void voidReturn() {
doAnswer(x -> {
x.getArgument(1, List.class).add("k");
return null;
})
.when(kissDao).findAllInto(any(), any());
ArrayList<Object> result = new ArrayList<>();
kissDao.findAllInto("", result);
System.out.println(result);
}
当然,do 系列还有很多别的,都是类似的.需要提到前面写的一般都用这个doXXX
.
静态方法怎么 mock?
前面的方法,都是在对象上的, mock 或者 spy 以后用 when 就可以了. 那么静态方法怎么处理?
比如,还是给KissDao
加方法:
public static int staticMethod(){
return 1;
}
这问题确实很棘手, 老版本的 mockito 对静态方法支持确实不太行,一般会采用powermock
的方案来解决. 不过 3.4 以后, mockito 支持了. 这里就不提powermock
了,直接上mockito-inline
.
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>3.8.0</version>
<scope>test</scope>
</dependency>
有这个包在,inline maker
就算是启用了, 测试可以借助try-with-resources
语法写成这样.
@Test
void mockStaticMethod(){
try(MockedStatic<KissDao> mock = mockStatic(KissDao.class)){
mock.when(KissDao::staticMethod) .thenReturn(75);
int n = KissDao.staticMethod();
assertEquals(n,75);
}
}
注意中间的过滤器写法,是个Method Reference
.如果静态方法有参数话,这里可能要换成 lambda.
mock.when(()->KissDao.staticMethod()) .thenReturn(75);
有参数的怎么写,我就不展开了.
final怎么处理
最早部分说过, 用子类
这种方式来做mock,解决不了final
的问题.有了inline-marker
以后, 可以了. 这里不再给例子.
自动注入怎么 mock
前面直接把KissDao
设置到KissService
里去,这是我个人习惯,为了测试,不太爱用private field
来做注入.
如果做了完全私有的属性控制, mockito 提供了一套完善解决办法,自动注入.
class KissServiceTest {
@Mock
KissDao kissDao;
@InjectMocks
KissService kissService;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
}
这里是注解语法,不用自己手动调用mock
,spy
之类了.
-
@Mock
当前属性是 mock 的 -
@Spy
当前需要 spy -
@InjectMocks
当前对象里需要把 mock 出来的内容给自动注入
这一套标记下来, 省了不少事.再调用openMocks
执行注入就好.
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
如果不愿意使用手动打开, 也可以直接使用runner,会自动进行这部分操作.需要写成这样.
@ExtendWith(MockitoExtension.class)
class KissServiceTest {
//...
}
有了这个注解以后,就不用自己手动打开了. 当然, 要是连这都懒得写, @MockitoSettings
注解加上也可以.
除了这些注解, 还有一个叫做@MockBean
的.这个主要用在Spring环境下,会把mock对象加入Context.怎么用就不提了.
verify & ArgumentCapture
还有一套验证用的verfify,和验证时候用来获取参数的ArgumentCapture. 我不太用他们,有需求的记得又这么个东西就行, 功能还是挺丰富的.
BDDMockito
前面提到的都是Mockito.class
上面的接口. 下面提一下另一个入口类BDDMockito
.
TDD
的T
指Test
, BDD
的B
指Behavior
. 这是一种测试风格, 更偏重业务角度.更倾向于用一种模式化的自然语言来描述规范. 讲究given.. when.. then
.
mockito项目自身的单测, 就是用这种方式写的.喜欢这种API风格的, 可以用.
Junit 小技巧
前面的都说完了,回头来说点儿junit
小技巧. 目前示例中出现的测试都是junit5
的测试.
参数化测试
如果熟悉testng, 这个应该很熟悉了.junit4时代的支持有点烂, 5代看起来好了点.
大概如下图可以提供两个方法, 一个方法做测试,同名静态方法提供参数.
static List<Arguments> add() {
return Arrays.asList(
Arguments.of(1,2,3)
,Arguments.of(1,2,3)
,Arguments.of(1,2,3)
,Arguments.of(1,2,3)
);
}
@ParameterizedTest
@MethodSource //不同名话可以手动指定
void add(int a, int b, int expect) {
assertEquals(a + b,expect );
}
这里的参数源,除了MethodSource
,也可以使用CSV
等作为输入源.这里自定义能力很强,需要的话可以自己研究.
assert 快速补完技巧
测试中有大量 assert 要写, 是个体力活.
为了简化操作, 可以编写一些测试相关的idea自定义postfix completion
模板,来加速这个流程.
当然,敲完回车,光标位置也可以自定义.比如assertEquals
,需要另一个参数.
具体定义成什么样, 还是看个人习惯, 我目前定制了下面几个,凑合够用.
Assertions.assertTrue($EXPR$); //at
Assertions.assertEquals($EXPR$,$END$); //ae
Assertions.assertNotNull($EXPR$); //ann

后话
以后有时间或许可以解读下 mockito 源码和 API 设计. 今天是纯实用性的技巧, 就到这里吧.
原文始发于微信公众号(K字的研究):3分钟入门mockito使用
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/24746.html