前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >junit4整合PowerMockito进行单元测试

junit4整合PowerMockito进行单元测试

作者头像
半月无霜
发布2023-10-18 16:33:57
8160
发布2023-10-18 16:33:57
举报
文章被收录于专栏:半月无霜半月无霜

junit4整合PowerMockito进行单元测试

一、介绍

在单元测试中,代码里面往往有一些需要连接数据库、调用第三方远程的代码。

由于没有环境,这些代码的存在,会给单元测试造成影响。

所以我们在单测中,往往会使用mock的方式对这些代码做一个数据的模拟,从而达到对代码进行测试的一个目的。

所以单测需要满足以下几点

  • 可复用:单测代码可以重复执行
  • 无环境:不要依赖数据库,第三方接口等外部的环境依赖
  • 方法级细粒度:单测代码应该针对具体一个方法的测试,
  • 高覆盖率:如果代码中复杂度过高,单测要覆盖到方法中的每一行代码
  • 自动断言:每一段单测代码都应该有自己的断言方法,而不是通过打印再人工查看正确性

所以我们就有了Mockito,它可以模拟对象,模拟对象方法的返回值,来完成mock

本文使用的是PowerMockito,它是由Mockito的基础上开发而来,语法规则基本一致,同时也有一些自己的增强,可以对静态方法,局部变量进行mock

二、初步入门

假设我们有下面这两段代码PowerMockitoServiceImpl.javaPowerMockitoMapper.java

代码语言:javascript
复制
package com.banmoon.test.service.impl;

import com.banmoon.test.entity.PowerMockitoEntity;
import com.banmoon.test.mapper.PowerMockitoMapper;
import com.banmoon.test.service.PowerMockitoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Optional;

@Service
public class PowerMockitoServiceImpl implements PowerMockitoService {

    @Autowired
    private PowerMockitoMapper powerMockitoMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void insert(PowerMockitoEntity entity) {
        Boolean status = Optional.ofNullable(entity.getValue()).map(a -> Boolean.TRUE).orElse(Boolean.FALSE);
        entity.setStatus(status);
        powerMockitoMapper.insert(entity);
    }

}
代码语言:javascript
复制
package com.banmoon.test.mapper;

import com.banmoon.test.entity.PowerMockitoEntity;

public interface PowerMockitoMapper {

    int insert(PowerMockitoEntity entity);
}

还有一段PowerMockitoEntity.java

代码语言:javascript
复制
package com.banmoon.test.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

@Data
@EqualsAndHashCode(callSuper = false)
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@TableName("power_mockito")
public class PowerMockitoEntity {

    @TableId
    private Integer id;

    private String value;

    private Boolean status;

}

上面代码所做的功能就是,插入一个实体至数据库。

在插入前,我们根据entity.value是否有值,给予entity.status的值


故此,上面的代码需要连接数据库,我们在单测时,直接对PowerMockitoMapper进行mock即可

首先,先导入依赖,根据自己的需要进行删减使用

代码语言:javascript
复制
<!-- powermock -->
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>3.3.3</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-api-mockito2</artifactId>
    <version>2.0.9</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-junit4</artifactId>
    <version>2.0.9</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-junit4-rule</artifactId>
    <version>2.0.9</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-classloading-xstream</artifactId>
    <version>2.0.9</version>
    <scope>test</scope>
</dependency>

代码如下

代码语言:javascript
复制
package com.banmoon.test.service.impl;

import com.banmoon.test.entity.PowerMockitoEntity;
import com.banmoon.test.mapper.PowerMockitoMapper;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.powermock.modules.junit4.PowerMockRunner;

import static org.mockito.Mockito.when;

@RunWith(PowerMockRunner.class)
public class PowerMockitoServiceImplTest {

    @Mock
    private PowerMockitoMapper mockPowerMockitoMapper;

    @InjectMocks
    private PowerMockitoServiceImpl powerMockitoServiceImplUnderTest;

    /**
     * 有值测试
     */
    @Test
    public void testInsert1() {
        // 设置参数
        final PowerMockitoEntity entity = new PowerMockitoEntity();
        entity.setId(1);
        entity.setValue("有值测试");
        when(mockPowerMockitoMapper.insert(entity)).thenReturn(1);

        // 执行测试
        powerMockitoServiceImplUnderTest.insert(entity);

        // 校验结果
        Assert.assertTrue(entity.getStatus());
    }

    /**
     * 无值测试
     */
    @Test
    public void testInsert2() {
        // 设置参数
        final PowerMockitoEntity entity = new PowerMockitoEntity();
        entity.setId(1);
        entity.setValue(null);
        when(mockPowerMockitoMapper.insert(entity)).thenReturn(1);

        // 执行测试
        powerMockitoServiceImplUnderTest.insert(entity);

        // 校验结果
        Assert.assertFalse(entity.getStatus());
    }
}

执行结果如下,保证两个测试方法如预期通过即可

image-20230327213425469
image-20230327213425469

三、其他使用

1)如何对无返回值的方法进行断言

假设有一个无返回值的方法,我们要针对它进行测试。由于它没有返回值,就没有办法对其返回值进行断言校验。

那么针对这种情况,一个方法,就算是无返回值的情况。内部一定做了一些什么操作。所以我们一般有两种方式

  • 这个方法做了设置某个对象的属性,我们可以校验对象属性是否符合预期
    • 如第二章的初步使用就是如此
  • 如果这个方法执行了某段逻辑分支的代码,我们可以可以校验那段代码是否执行过

PowerMockitoServiceImpl.java上再加一个无返回值的方法

代码语言:javascript
复制
@Service
public class PowerMockitoServiceImpl implements PowerMockitoService {

    @Autowired
    private PowerMockitoMapper powerMockitoMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveOrUpdate(PowerMockitoEntity entity) {
        if (entity.getId() == null) {
            powerMockitoMapper.insert(entity);
        } else {
            powerMockitoMapper.updateById(entity);
        }
    }
}

PowerMockitoMapper.java如下

代码语言:javascript
复制
package com.banmoon.test.mapper;

import com.banmoon.test.entity.PowerMockitoEntity;

public interface PowerMockitoMapper {

    int insert(PowerMockitoEntity entity);

    int updateById(PowerMockitoEntity entity);
}

那么我们可以这样

代码语言:javascript
复制
package com.banmoon.test.service.impl;

import com.banmoon.test.entity.PowerMockitoEntity;
import com.banmoon.test.mapper.PowerMockitoMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.powermock.modules.junit4.PowerMockRunner;

import static org.mockito.Mockito.when;

@RunWith(PowerMockRunner.class)
public class NoneReturnTest {

    @Mock
    private PowerMockitoMapper mockPowerMockitoMapper;

    @InjectMocks
    private PowerMockitoServiceImpl powerMockitoServiceImplUnderTest;

    /**
     * 有ID测试
     */
    @Test
    public void test1() {
        // 设置参数
        final PowerMockitoEntity entity = new PowerMockitoEntity();
        entity.setId(1);
        entity.setValue("测试");
        when(mockPowerMockitoMapper.updateById(entity)).thenReturn(1);

        // 执行测试
        powerMockitoServiceImplUnderTest.saveOrUpdate(entity);

        // 校验结果
        Mockito.verify(mockPowerMockitoMapper).updateById(entity);
    }

    /**
     * 没有ID测试
     */
    @Test
    public void test2() {
        // 设置参数
        final PowerMockitoEntity entity = new PowerMockitoEntity();
        entity.setId(null);
        entity.setValue("测试");
        when(mockPowerMockitoMapper.insert(entity)).thenReturn(1);

        // 执行测试
        powerMockitoServiceImplUnderTest.saveOrUpdate(entity);

        // 校验结果
        Mockito.verify(mockPowerMockitoMapper).insert(entity);
    }
}

2)对属局部对象进行mock并设置

如果一个方法中,有一个自己实例化的一个局部变量,那么我们该如何对其进行mock呢?

例如下面这个方法,有一个自己的局部变量tuple,并返回了这个局部变量的数量

代码语言:javascript
复制
package com.banmoon.service.impl;

import com.banmoon.service.PowerMockitoService;
import org.springframework.stereotype.Service;

import java.io.File;

@Service
public class PowerMockitoServiceImpl implements PowerMockitoService {

    @Override
    public long localVariable() {
        File file = new File("E://abc.txt");
        return file.length();
    }

}

我们只需要这样进行,即可以完成对局部变量的mock

代码语言:javascript
复制
package com.banmoon.test.powerMockitoTest;

import com.banmoon.service.impl.PowerMockitoServiceImpl;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import java.io.File;

@RunWith(PowerMockRunner.class)
@PrepareForTest({PowerMockitoServiceImpl.class})
public class LocalVariableTest {

    @InjectMocks
    private PowerMockitoServiceImpl powerMockitoServiceImplUnderTest;

    @Test
    public void localVariableTest() throws Exception {
        // 设置参数
        File file = PowerMockito.mock(File.class);

        // mock
        PowerMockito.whenNew(File.class)
                .withAnyArguments()
                .thenReturn(file);
        PowerMockito.when(file.length()).thenReturn(2L);

        // 执行测试
        long i = powerMockitoServiceImplUnderTest.localVariable();

        // 校验结果
        Assert.assertEquals(2L, i);
    }

}

3)对静态方法mock

如何对静态方法的返回值进行mock

先在PowerMockitoServiceImpl.java添加一个静态方法,其中发现HttpUtil.get()是一个静态方法

代码语言:javascript
复制
@Service
public class PowerMockitoServiceImpl implements PowerMockitoService {

    @Autowired
    private PowerMockitoMapper powerMockitoMapper;

    @Override
    public int syncPowerMockitoEntity() {
        String result = HttpUtil.get("url");
        if (CharSequenceUtil.isNotBlank(result)) {
            List<JSONObject> list = JSON.parseObject(result, List.class);
            int i = 0;
            for (JSONObject json : list) {
                PowerMockitoEntity entity = json.toJavaObject(PowerMockitoEntity.class);
                i += powerMockitoMapper.insert(entity);
            }
            return i;
        } else {
            throw new BanmoonException(1001, "同步出现异常");
        }
    }

}

针对上面的方法,我们可以这样进行mock,注意@PrepareForTest注解,一定要写上,改变了其中的字节码

代码语言:javascript
复制
package com.banmoon.test.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.banmoon.test.entity.PowerMockitoEntity;
import com.banmoon.test.mapper.PowerMockitoMapper;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
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.List;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

@RunWith(PowerMockRunner.class)
@PrepareForTest(HttpUtil.class)
public class StaticMethodTest {

    @Mock
    private PowerMockitoMapper mockPowerMockitoMapper;

    @InjectMocks
    private PowerMockitoServiceImpl powerMockitoServiceImplUnderTest;

    /**
     * 静态方法mock
     */
    @Test
    public void test() {
        // 设置参数
        final PowerMockitoEntity entity1 = new PowerMockitoEntity();
        entity1.setId(1);
        entity1.setValue("测试1");
        final PowerMockitoEntity entity2 = new PowerMockitoEntity();
        entity2.setId(2);
        entity2.setValue("测试2");
        List<PowerMockitoEntity> list = CollUtil.newArrayList(entity1, entity2);

        // mock
        PowerMockito.mockStatic(HttpUtil.class);
        when(HttpUtil.get(any())).thenReturn(JSON.toJSONString(list));
        when(mockPowerMockitoMapper.insert(any())).thenReturn(1);

        // 执行测试
        int i = powerMockitoServiceImplUnderTest.syncPowerMockitoEntity();

        // 校验结果
        Assert.assertEquals(2, i);
    }
}

4)mock final修饰的类和方法

首先我们先写一个工具类,这个工具类是final修饰的,里面的方法也是final

代码语言:javascript
复制
package com.banmoon.util;

import cn.hutool.core.util.RandomUtil;

public final class PowerMockitoUtil {

    public final String finalMethod(int length) {
        return RandomUtil.randomString(length);
    }

}

单测这个方法

代码语言:javascript
复制
package com.banmoon.service.impl;

import com.banmoon.service.PowerMockitoService;
import com.banmoon.util.PowerMockitoUtil;
import org.springframework.stereotype.Service;

@Service
public class PowerMockitoServiceImpl implements PowerMockitoService {

    private final PowerMockitoUtil powerMockitoUtil = new PowerMockitoUtil(10);

    @Override
    public int finalMethod() {
        String method = powerMockitoUtil.finalMethod();
        return method.length();
    }

}

测试类

代码语言:javascript
复制
package com.banmoon.test.powerMockitoTest;

import com.banmoon.service.impl.PowerMockitoServiceImpl;
import com.banmoon.util.PowerMockitoUtil;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import java.lang.reflect.Field;

@RunWith(PowerMockRunner.class)
@PrepareForTest({PowerMockitoServiceImpl.class, PowerMockitoUtil.class})
public class FinalMethodTest {

    @InjectMocks
    private PowerMockitoServiceImpl powerMockitoServiceImplUnderTest;

    @Test
    public void finalMethodTest() throws Exception {
        // 设置参数
        PowerMockitoUtil util = PowerMockito.mock(PowerMockitoUtil.class);

        // mock
        Field field = PowerMockito.field(PowerMockitoServiceImpl.class, "powerMockitoUtil");
        field.set(powerMockitoServiceImplUnderTest, util);
        PowerMockito.when(util.finalMethod()).thenReturn("abcde");

        // 执行测试
        int i = powerMockitoServiceImplUnderTest.finalMethod();

        // 校验结果
        Assert.assertEquals(5, i);
    }

}

5)异常的情况

有些时候,代码是会发生异常的,那么在单测的环境下,我们需要判断这些异常是什么,是不是符合预期

如下这个方法,我们只需要传个null,就会发生NullPointException的异常

代码语言:javascript
复制
package com.banmoon.service.impl;

import com.banmoon.service.PowerMockitoService;
import org.springframework.stereotype.Service;

@Service
public class PowerMockitoServiceImpl implements PowerMockitoService {

    @Override
    public int exceptionMethod(String name) {
        return name.length();
    }

}

测试用例

代码语言:javascript
复制
package com.banmoon.test.powerMockitoTest;

import com.banmoon.service.impl.PowerMockitoServiceImpl;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest({PowerMockitoServiceImpl.class})
public class ExceptionMethodTest {

    @InjectMocks
    private PowerMockitoServiceImpl powerMockitoServiceImplUnderTest;

    @Rule
    public ExpectedException thrown = ExpectedException.none();

    @Test
    public void exceptionMethodTest() throws Exception {
        // 校验结果
        Assert.assertThrows(NullPointerException.class, () -> {
            // 执行测试
            int i = powerMockitoServiceImplUnderTest.exceptionMethod(null);
        });
    }

    @Test
    public void exceptionMethodTest2() throws Exception {
        // 校验结果
        thrown.expect(NullPointerException.class);

        // 执行测试
        int i = powerMockitoServiceImplUnderTest.exceptionMethod(null);
    }

}

四、最后

推荐一个很好用的IDEA插件,这个插件可以快速生成单元测试代码

squaretest

我是半月,你我一同共勉!!!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-09-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • junit4整合PowerMockito进行单元测试
    • 一、介绍
      • 二、初步入门
        • 三、其他使用
          • 1)如何对无返回值的方法进行断言
          • 2)对属局部对象进行mock并设置
          • 3)对静态方法mock
          • 4)mock final修饰的类和方法
          • 5)异常的情况
        • 四、最后
        相关产品与服务
        腾讯云服务器利旧
        云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档