junit4整合PowerMockito进行单元测试
一、介绍
在单元测试中,代码里面往往有一些需要连接数据库、调用第三方远程的代码。
由于没有环境,这些代码的存在,会给单元测试造成影响。
所以我们在单测中,往往会使用mock
的方式对这些代码做一个数据的模拟,从而达到对代码进行测试的一个目的。
所以单测需要满足以下几点
-
可复用:单测代码可以重复执行
-
无环境:不要依赖数据库,第三方接口等外部的环境依赖
-
方法级细粒度:单测代码应该针对具体一个方法的测试,
-
高覆盖率:如果代码中复杂度过高,单测要覆盖到方法中的每一行代码
-
自动断言:每一段单测代码都应该有自己的断言方法,而不是通过打印再人工查看正确性
所以我们就有了Mockito
,它可以模拟对象,模拟对象方法的返回值,来完成mock
。
本文使用的是PowerMockito
,它是由Mockito
的基础上开发而来,语法规则基本一致,同时也有一些自己的增强,可以对静态方法,局部变量进行mock
。
二、初步入门
假设我们有下面这两段代码PowerMockitoServiceImpl.java
和PowerMockitoMapper.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 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); }
}
|
1 2 3 4 5 6 7 8
| package com.banmoon.test.mapper;
import com.banmoon.test.entity.PowerMockitoEntity;
public interface PowerMockitoMapper {
int insert(PowerMockitoEntity entity); }
|
还有一段PowerMockitoEntity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 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
即可
首先,先导入依赖,根据自己的需要进行删减使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <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>
|
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| 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()); } }
|
执行结果如下,保证两个测试方法如预期通过即可
三、其他使用
1)如何对无返回值的方法进行断言
假设有一个无返回值的方法,我们要针对它进行测试。由于它没有返回值,就没有办法对其返回值进行断言校验。
那么针对这种情况,一个方法,就算是无返回值的情况。内部一定做了一些什么操作。所以我们一般有两种方式
PowerMockitoServiceImpl.java
上再加一个无返回值的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @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
如下
1 2 3 4 5 6 7 8 9 10
| package com.banmoon.test.mapper;
import com.banmoon.test.entity.PowerMockitoEntity;
public interface PowerMockitoMapper {
int insert(PowerMockitoEntity entity);
int updateById(PowerMockitoEntity entity); }
|
那么我们可以这样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| 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;
@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); }
@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
,并返回了这个局部变量的数量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| 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);
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()
是一个静态方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @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
注解,一定要写上,改变了其中的字节码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| 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;
@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);
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
的
1 2 3 4 5 6 7 8 9 10 11
| package com.banmoon.util;
import cn.hutool.core.util.RandomUtil;
public final class PowerMockitoUtil {
public final String finalMethod(int length) { return RandomUtil.randomString(length); }
}
|
单测这个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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(); }
}
|
测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| 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);
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
的异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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(); }
}
|
测试用例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| 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
我是半月,你我一同共勉!!!