当前位置: 首页 > article >正文

基于mockito做单元测试

1.简介

  • 配合断言使用(杜绝System.out)
  • 可重复执行
  • 不依赖环境
  • 不会对数据产生影响
  • Spring的上下文环境不是必备的
  • 一般都配合mock类框架对数据库进行隔离

mock类使用场景:

要进行测试的方法存在外部依赖(DB,Redis,第三方接口),为了专注于对该方法的逻辑进行测试,就希望能隔离出来外部依赖,避免外部依赖成为单测的阻塞项,一般单测都是在service进行测试

创建mock对象的三种方法:

/**
 * 创建mock对象的第一种方法
 */
@ExtendWith(MockitoExtension.class)
public class InitMockOrSpyMethod1 {
    @Mock
    private UserService mockUserService;
    @Spy
    private UserService spyUserServices;

    @Test
    public void test1(){
        System.out.println("Mockito.mockingDetails(mockUserService).isMock():"+Mockito.mockingDetails(mockUserService).isMock());
        System.out.println("Mockito.mockingDetails(mockUserService).isSpy():"+Mockito.mockingDetails(mockUserService).isSpy());
        System.out.println("Mockito.mockingDetails(spyUserServiceS).isMock():"+Mockito.mockingDetails(spyUserServices).isMock());
        // spy对象是另一种类型的mock对象
        System.out.println("Mockito.mockingDetails(spyUserServiceS).isSpy():"+Mockito.mockingDetails(spyUserServices).isSpy());
    }

}
/**
 * 创建mock对象的第二种方法
 */
public class InitMockOrSpyMethod2 {
    private UserService mockUserService;
    private UserService spyUserServices;

    @BeforeEach
    public void init(){
        mockUserService = Mockito.mock(UserService.class);
        spyUserServices = Mockito.spy(UserService.class);
    }

    @Test
    public void test1(){
        System.out.println("Mockito.mockingDetails(mockUserService).isMock():"+Mockito.mockingDetails(mockUserService).isMock());
        System.out.println("Mockito.mockingDetails(mockUserService).isSpy():"+Mockito.mockingDetails(mockUserService).isSpy());
        System.out.println("Mockito.mockingDetails(spyUserServiceS).isMock():"+Mockito.mockingDetails(spyUserServices).isMock());
        // spy对象是另一种类型的mock对象
        System.out.println("Mockito.mockingDetails(spyUserServiceS).isSpy():"+Mockito.mockingDetails(spyUserServices).isSpy());
    }
}
/**
 * 创建mock对象的第三种方法
 */
public class InitMockOrSpyMethod3 {
    @Mock
    private UserService mockUserService;
    @Spy
    private UserService spyUserServices;

    @BeforeEach
    public void init(){
        MockitoAnnotations.openMocks(this);
    }

    @Test
    public void test1(){
        System.out.println("Mockito.mockingDetails(mockUserService).isMock():"+Mockito.mockingDetails(mockUserService).isMock());
        System.out.println("Mockito.mockingDetails(mockUserService).isSpy():"+Mockito.mockingDetails(mockUserService).isSpy());
        System.out.println("Mockito.mockingDetails(spyUserServiceS).isMock():"+Mockito.mockingDetails(spyUserServices).isMock());
        // spy对象是另一种类型的mock对象
        System.out.println("Mockito.mockingDetails(spyUserServiceS).isSpy():"+Mockito.mockingDetails(spyUserServices).isSpy());
    }
}

2.mock和spy对象

mock对象:

  • 方法插桩:执行插桩逻辑
  • 方法不插桩:返回mock对象的默认值
  • 作用对象:类吗,接口

spy对象:

  • 方法插桩:执行插桩逻辑
  • 方法不插桩:调用真实方法
  • 作用对象:类吗,接口

3.参数匹配

@Test
public void test1() {
    UserDO userDO = new UserDO();
    userDO.setId(1L);
    doReturn("nothing").when(mockUserService).selectNameById(userDO.getId());
    // 只有执行Mockito.doReturn才执行插桩
    System.out.println(mockUserService.selectNameById(userDO.getId()));
    // userDO2对象不进行插桩,返回默认值
    UserDO userDO2 = new UserDO();
    userDO2.setId(2L);
    System.out.println(mockUserService.selectNameById(userDO2.getId()));
}
    /**
     * 此时我只想拦截UserDO类型的任意对象
     * ArgumentMatchers.*拦截任意类型
     */
    @Test
    public void test2() {
        doReturn("nothingName").when(mockUserService).selectNameById(ArgumentMatchers.anyLong());
        UserDO userDO = new UserDO();
        userDO.setId(1L);
        System.out.println(mockUserService.selectNameById(userDO.getId()));
        UserDO userDO2 = new UserDO();
        userDO2.setId(2L);
        System.out.println(mockUserService.selectNameById(userDO2.getId()));
    }

方法插桩

指定调用某个方法时的行为,达到相互隔离的效果

  • 返回指定值
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
public class StubTest {
    @Mock
    private List<String> mockList;
    /**
     * 指定返回值
     */
    @Test
    public void test1() {
        // 方法一
        doReturn("zero").when(mockList).get(0);
        // 如果返回值不相等,则测试不通过
        Assertions.assertEquals("zero", mockList.get(0));

        //方法二
        when(mockList.get(1)).thenReturn("one");
        Assertions.assertEquals("one", mockList.get(1));
    }
}
  • void返回值方法插桩
/**
  * void 返回值插桩
  */
@Test
public void test2() {
    // 调用mockList.clear()什么也没做
    doNothing().when(mockList).clear();
    mockList.clear();
    verify(mockList, times(1)).clear();
}
  • 插桩的两种形式

    • when(obj.someMethod.thenXXX):用于mock对象
    • doXXX():用于mock/spy对象

    区别在于不插桩时doXXX应用与spy对象时会调用真实方法

    public void test4() {
        when(mockUserServiceImpl.getNumber()).thenReturn(99);
        System.out.println("mockUserServiceImpl.getNumber():"+mockUserServiceImpl.getNumber());
        doReturn(99).when(spyUserServiceImpl).getNumber();
        System.out.println("spyUserServiceImpl.getNumber() = " + spyUserServiceImpl.getNumber());
    }
    
  • 抛异常

/**
     * 断言异常
     */
    @Test
    public void test3() {
        doThrow(RuntimeException.class).when(mockList).get(anyInt());
        try{
            mockList.get(10000);//此处get(10000)为空
            Assertions.fail();
        }catch (RuntimeException e){
            // 断言表达式为真
            Assertions.assertTrue(e instanceof RuntimeException);
        }
    }
  • 多次插桩
/**
     * 多次插桩
     */
    @Test
    public void test4() {
        // 第一次调用返回1,第二次返回2,第三次返回3以及以后得调用都返回三
        when(mockList.size()).thenReturn(1)
                .thenReturn(2)
                .thenReturn(3);
        Assertions.assertEquals(1,mockList.size());
        Assertions.assertEquals(2,mockList.size());
        Assertions.assertEquals(3,mockList.size());
        Assertions.assertEquals(4,mockList.size());
    }
  • thenAnswer
/**
     * thenAnswer:实现指定的插桩逻辑
     */
    @Test
    public void test5() {
        // 此处不管传入什么参数,都返回100*3
        when(mockList.get(anyInt())).thenAnswer(new Answer<String>() {
            /**
             * 泛型是要返回插桩的返回值类型
             * @param invocationOnMock
             * @return
             * @throws Throwable
             */
            @Override
            public String answer(InvocationOnMock invocationOnMock) throws Throwable {
                Integer argument = invocationOnMock.getArgument(0, Integer.class);
                return String.valueOf(argument*100);
            }
        });
        String s = mockList.get(3);
        Assertions.assertEquals("300",s);
    }
  • 执行真正的原始方法
/**
     * 执行真正的原始方法
     */
    @Test
    public void test6() {
        when(mockUserServiceImpl.getNumber()).thenCallRealMethod();
        int number = mockUserServiceImpl.getNumber();
        Assertions.assertEquals(1,number);
    }
  • verify的使用
    /**
     * 指定方法返回几次
     */
    @Test
    public void test3() {
        mockUserService.add("lily", "123123", new ArrayList<>());
        // 验证add方法被调用了1次
        Mockito.verify(mockUserService, Mockito.times(1))
                .add("lily", "123123", new ArrayList<>());
        // 要么全部使用ArgumentMatchers,不能一半使用参数,一半使用ArgumentMatchers
        Mockito.verify(mockUserService, Mockito.times(1))
                .add(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyList());
    }

4.@InjectMocks注解

  • 作用:如果@InjectMocks声明的变量需要用到mock,spy对象,mockito会自动使用当前类里的mock或者spy成员进行an类型或者名字注入
  • 原理:构造器注入,setter注入,字段反射注入
@ExtendWith(MockitoExtension.class)
public class InjectMocksTest {

    /**
     * 被InjectMocks标注的属性,必须是实现类,因为Mockito会创建InjectMocks注解的类的实例,
     * 并注入到被InjectMocks标注的属性中,默认创建的对象就是没有经过Mockito处理过的对象,因此
     * 配合@Spy注解,变成可以调用默认方法的mock对象
     */
    @InjectMocks
    @Spy
    private UserServiceImpl userService;

    @Mock
    private UserFeatureService userFeatureService;

    @Test
    public void test() {
        int number = userService.getNumber();
        Assertions.assertEquals(1, number);
    }
}

http://www.kler.cn/news/314570.html

相关文章:

  • 16【Protues51单片机仿真】智能洗衣机倒计时系统
  • 【如何在 Windows 10 主机上通过 VMware 安装 Windows 11 虚拟机,并共享主机网络】
  • ftp服务的管理及安全优化
  • Google 扩展 Chrome 安全和隐私功能
  • C/C++通过CLion2024进行Linux远程开发保姆级教学
  • io多路复用:epoll水平触发(LT)和边沿触发(ET)的区别和优缺点
  • Linux 自旋锁
  • Spring Mybatis 动态语句 总结
  • 简单生活的快乐
  • (k8s)kubernetes集群基于Containerd部署
  • Flask-SQLAlchemy一对多 一对一 多对多关联
  • GDPU Andriod移动应用 Activity
  • 【数据结构与算法】LeetCode:哈希表
  • Alinx MPSoC驱动开发第17章I2C实验修改设备树后petalinux编译报错
  • 分布式Id生成策略-美团Leaf
  • 使用python对图像批量水平变换和垂直变换
  • 深度学习参数管理
  • MySQL-DDL/DML(数据定义/操作语言)
  • GIS开发之如何使用OpenLayers,Leaflet,Mapbox,Cesium,ArcGIS, Turf.js 与 D3.js
  • 【Webpack--00802】配置Babel语法兼容
  • 【图像检索】基于Gabor特征的图像检索,matlab实现
  • Python面试宝典第50题:分割等和子集
  • Vscode、插件历史版本下载
  • [数据结构与算法·C++] 笔记 1.4 算法复杂性分析
  • [附源码]SpringBoot+VUE+Java实现人脸识别系统
  • 实战指南:深度剖析Servlet+JSP+JDBC技术栈下的用户CRUD操作
  • 探秘 Web Bluetooth API:连接蓝牙设备的新利器
  • 828华为云征文|Flexus X实例GitLab部署构建流水线-私人一体化代码仓库~
  • AWS账号可以共用吗?
  • vue 中互相不关联的两个组件怎么进行通信(数据传输)