Mockito单元测试

Stella981
• 阅读 650

Mockito简介

Mockito是一个单元测试框架,需要Junit的支持。在我们的项目中,都存在相当多的依赖关系,当我们在测试某一个业务相关的接口或则方法时,绝大多数时候是没有办法或则很难去添加所有的依赖,因为这中间肯定会涉及到别的业务逻辑。而在开发过程中,可能这个模块根本都还没有。那可咋怎啊?这个时候一种叫做mock测试的方式就顺势崛起。通过模拟出依赖对象,并对涉及到的方法设置预期值。这样你就可以只关心依赖方法的结果,从而完成对本模块的单元测试。这种方法还细化了测试粒度。棒棒的。想做更多了解就自行解决了。

Mockito的使用

  1. 首先来一个最基本的Junit测试

    @Test public void stringUtilTest(){ boolean b = StringUtil.isEmpty("good"); Assert.assertTrue("must true",b);//断言 }

  像这种对工具类的测试,一般很少依赖别的类,所以直接断言之。当然断言的类型还有很多,这里就试用一下对boolean的断言。

  2.  mockito对依赖的模拟,并设置预期返回值。针对依赖的方法有返回值。在单元测试时,我们不想也最好不要直接调用依赖的方法的具体实现,因为所依赖的方法可能本来就没有经过测试,还存在bug,难道这时候又要为依赖的方法再写一个test case?或则这个方法由别人开发,但是目前还没有实现,难道要自己去实现?you`d better say NO!看看下面怎么做的。

import org.mockito.Mockito;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @auther guozg
 */

public class MockTest {
    @Test
    public void mockReturnTest(){
        // mock creation 创建模拟对象
        UserDao mockeDao = Mockito.mock(UserDao.class);
        UserService s = new UserService();//创建被测试类
        s.setDao(mockeDao);//为被测试类添加依赖
        Mockito.when(mockeDao.getData(Mockito.anyString())).thenReturn(4).thenReturn(1);//为模拟对象方法设置预期返回值,
        boolean b = s.checkDate();                          //多个thenReturn表示多次调用时,依次返回
        boolean b1 = s.checkDate();              //如果设置的预期个数少于调用次数,超过的调用都返回最后一个。
        boolean b2 = s.checkDate();              //如果设置的预期个数多于调用次数,任然依次返回相应值
        Mockito.verify(mockeDao, Mockito.times(1)).getData(Mockito.anyString());
        Assert.assertTrue("must true",b);
        Assert.assertFalse("must true",b1);
        Assert.assertFalse("must true",b2);
    }
}

class UserService{
    UserDao dao ;
    public boolean checkDate(){     String id = "123";
        if(dao.getData(id)>3){
            return true;
        }
        return false;
    }

    public UserDao getDao() {
        return dao;
    }

    public void setDao(UserDao dao) {
        this.dao = dao;
    }

}

class UserDao{
    public Integer getData(String id){ return 0; } }

  这里随便举的简单例子,我需要对UserService.checkDate()做一个单元测试。而这时候UserService对UserDao存在依赖关系。所以这时候为了隔离UserDao的实现,通过Mockito.mock模拟出UserDao对象。并为掉用的getData()设置了预期返回值。然后调用要测试的方法、验证设置预期的方法是否被调用,最后对测试方法的返回值断言。这里指的一说的是Mockito.anyString()这个方法,他的主要目的是表示在模拟状态下,getData()的参数可以是任意字符串。当然也可以直接给定一些参数,如果模拟对象指定的参数和实际逻辑给的参数不一致,这时候不会返回实际UserDao中getData()的值也不会返回设置的预期值,而是返回的返回类型的默认值,比如int就返回0,boolean返回false等。Mockito还有很多类似的方法,比如anyInt(),anyBoolen(),anyCollection()等等。

  还有就是看到thenReturn()方法,这是在为依赖方法设置预期返回值,这个就可以有自己控制了。然后看到可以连续多次设置,这个在代码里面有注释了,由于对语言表达能力的不自信,来个表格展示一下。

                Mockito单元测试

  然后还有一个验证方法是否被调用Mockito.verify()这个方法的参数是模拟的对象、VerificationMode。这个VerificationMode就是一个验证模型,可以是代表调用次数、是否调用、超时验证,等等(有些我也不知道干啥的,要进一步研究)。verify()可以理解为断言。

  3.  对于有返回值的方法,我们可以通过设置预期来控制,并且隔离掉具体的实现。但是对于没有返回值得方法呢?不着急,Mockti为我们提供了另一种方式可以控制模拟对象的方法的行为,包括逻辑处理、抛异常、返回值、执行原方法逻辑以及什么都不做。doReturn()|doThrow()| doAnswer()|doNothing()|doCallRealMethod();下面先来测试一下doAnswer();首先,我们会在UserDao 中加一个setUser(User u)方法,为user设置年龄,然后通过UserService的checkData中调用。然后我们通过doAnwser去控制setUser的逻辑。

package com.centnet.train.user.controller;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @auther guozg
 */

public class MockTest {
   

    @Test
    public void doAnwserTest(){
        // mock creation 创建模拟对象
        UserDao mockeDao = Mockito.mock(UserDao.class);
        UserService s = new UserService();//创建被测试类
        User u = new User();
        s.setDao(mockeDao);//为被测试类添加依赖
        Mockito.doAnswer(new Answer() {
            public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
                User u = invocationOnMock.getArgument(0);
                u.setName("ggg");
                return null;
            }
        }).when(mockeDao).setUser(u);
        Mockito.when(mockeDao.getData()).thenReturn(4);
        boolean b = s.checkDate(u);

        Mockito.verify(mockeDao, Mockito.times(1)).getData();
        Mockito.verify(mockeDao, Mockito.times(1)).setUser(u);
        Assert.assertTrue("must true",b);
        Assert.assertNotNull("控制不成功",u.getName());        Assert.assertNull("原逻辑被执行",u.getAge());
    }
}

class UserService{
    UserDao dao ;
    public boolean checkDate(User u){
        dao.setUser(u);
        if(dao.getData()>3){
            return true;
        }
        return false;
    }

    public UserDao getDao() {
        return dao;
    }

    public void setDao(UserDao dao) {
        this.dao = dao;
    }

    public void setUser(User user){
        user.setAge(12);

    }

}

class UserDao{
    public Integer getData(){
        return 0;
    }

    public void setUser(User user){
        user.setAge(12);
    }
}

class User{

    String name;
    Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

  我们在UserDao中只设置了age,但是对age为空的断言却成功了。这表示原逻辑没有执行。再看看对Name的不为空断言,也通过了。这表示对setUser的行为被doAnwser()控制了。doAnwser()需要传入对一个Anwser对象,这个对象有点和代理对象类似。如果调用方法有返回值,则anwer()的返回值就是它。invocationOnMock.getArgument(0)获取参数,然后就可以对参数操作啦啦啦啦。其他几个也可以试试,比如改成调用原逻辑,Mockito.doCallRealMethod().when(mockeDao).setUser(u)。哎呀呀,这时候user的age就有了而且还是18.重点:对于在测试;类中的方法不用设置预期,会调用原逻辑,但是对于依赖类的方法就要设置预期了,否则会对有返回值的仅返回类型默认值、无返回值的直接啥都不做走人。

  好了,下班了,先到这里,以后有新的接触,在添加进来,以上例子略显粗犷,如不慎入坑,请包涵!(说得好像有人会看似的!!!要真有人看,基于以上例子可灵活处理。)

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这