为什么使用Mockito
项目中代码需要进行单元测试。测试人员用黑盒测试的软件功能,无法完全覆盖代码中的逻辑,所以要有代码的单元测试。
spring 的单元测试有以下缺点:
1.每次执行测试方法都必须启动spring容器。当项目规模较大、配置较为复杂时,会很慢。
2.如果使用数据库如mysql, 还会涉及插入数据、数据库自增ID增加问题、bug引起的大量脏数据
3.如果使用数据库,需要根据ID查询数据,必须要插入一条数据,每次执行单元测试都需要
如果只想测试service代码的正确性,使用Mockito就可以解决。
spring 的单元测试也是有用的,对容器、对组件的验证还是需要的。想要测试哪里选择不同的单元测试工具。
测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class TestDomain {
private Long id;
private String name;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
|
1 2 3 4 5 6 7 8 9
| public interface TestMapper {
Long getMax();
TestDomain getById(Long id);
int save(TestDomain testDomain);
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class TestService {
private TestMapper testMapper;
public Long method1() { Long id = testMapper.getMax(); return id; }
public void save(TestDomain testDomain) { int i = testMapper.save(testDomain); if (i <= 0) { throw new RuntimeException("插入失败"); } }
public TestDomain getById(Long id) { return testMapper.getById(id); }
}
|
使用Mockito
https://github.com/mockito/mockito
1 2 3 4 5 6 7 8 9 10 11 12
| <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>2.23.0</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</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
| import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class) public class TestServiceTest {
@InjectMocks private TestService testService;
@Mock public TestMapper testMapper;
@Test public void method1() { Mockito.when(testMapper.getMax()).thenReturn(20L); long id = testService.method1(); Assert.assertEquals(id, 20L); }
@Test public void save() { TestDomain testDomain = new TestDomain(); testDomain.setId(39L); testDomain.setName("39"); Mockito.when(testMapper.save(testDomain)).thenReturn(1); testService.save(testDomain);
Mockito.when(testMapper.save(testDomain)).thenReturn(0);
try { testService.save(testDomain); } catch (RuntimeException e) { Assert.assertEquals("插入失败", e.getMessage()); } }
@Test public void getById() { TestDomain testDomain = new TestDomain(); testDomain.setId(39L); testDomain.setName("39"); Mockito.when(testMapper.getById(39L)).thenReturn(testDomain); TestDomain testDomain1 = testService.getById(39L); Assert.assertEquals(testDomain, testDomain1); }
}
|
使用powermock
去掉mockito-core、junit依赖,在pom.xml中添加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <powermock.version>2.0.2</powermock.version> </properties>
<dependencies> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>${powermock.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito2</artifactId> <version>${powermock.version}</version> <scope>test</scope> </dependency> </dependencies>
|
因为powermock内已经依赖这两个包了。
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
| import org.junit.Assert; 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;
@RunWith(PowerMockRunner.class) public class TestServiceTest {
@InjectMocks private TestService testService;
@Mock public TestMapper testMapper;
@Test public void method1() { Mockito.when(testMapper.getMax()).thenReturn(20L); long id = testService.method1(); Assert.assertEquals(id, 20L); }
@Test public void save() { TestDomain testDomain = new TestDomain(); testDomain.setId(39L); testDomain.setName("39"); Mockito.when(testMapper.save(testDomain)).thenReturn(1); testService.save(testDomain);
Mockito.when(testMapper.save(testDomain)).thenReturn(0);
try { testService.save(testDomain); } catch (RuntimeException e) { Assert.assertEquals("插入失败", e.getMessage()); } }
@Test public void getById() { TestDomain testDomain = new TestDomain(); testDomain.setId(39L); testDomain.setName("39"); Mockito.when(testMapper.getById(39L)).thenReturn(testDomain); TestDomain testDomain1 = testService.getById(39L); Assert.assertEquals(testDomain, testDomain1); }
}
|
spring boot 与powermock整合
因为spring boot的父pom中已经引入了mockito,所以要注意,如果运行时报类不存在问题,可以明确加入mockito及版本解决。
介绍
org.mockito.Mockito
org.mockito.Mockito是mockito提供的核心api,提供了大量的静态方法,用于帮助我们来mock对象,验证行为等等,然后需要注意的是,很多方法都被封装在了MockitoCore类里面,下面对一些常用的方法做一些介绍。
mock:构建一个我们需要的对象;可以mock具体的对象,也可以mock接口。
spy:构建监控对象
verify:验证某种行为
when:当执行什么操作的时候,一般配合thenXXX 一起使用。表示执行了一个操作之后产生什么效果。
doReturn:返回什么结果
doThrow:抛出一个指定异常
doAnswer:做一个什么相应,需要我们自定义Answer;
times:某个操作执行了多少次
atLeastOnce:某个操作至少执行一次
atLeast:某个操作至少执行指定次数
atMost:某个操作至多执行指定次数
atMostOnce:某个操作至多执行一次
doNothing:不做任何处理
doReturn:返回一个结果
doThrow:抛出一个指定异常
doAnswer:指定一个操作,传入Answer
doCallRealMethod:返回真实业务执行的结果,只能用于监控对象
ArgumentMatchers
用于进行参数匹配,减少很多不必要的代码
anyInt:任何int类型的参数,类似的还有anyLong/anyByte等等。
eq:等于某个值的时候,如果是对象类型的,则看toString方法
isA:匹配某种类型
matches:使用正则表达式进行匹配
OngoingStubbing
OngoingStubbing用于返回操作的结果。
thenReturn:指定一个返回的值
thenThrow:抛出一个指定异常
then:指定一个操作,需要传入自定义Answer;
thenCallRealMethod:返回真实业务执行的结果,只能用于监控对象。
注解
@RunWith
在测试类类名上添加 @RunWith(PowerMockRunner.class) 注解代表该测试类使用 PowerMock。必须添加。
@PrepareForTest
这个注解的作用就是告诉 PowerMock 哪些类是需要在字节码级别上进行操作的。也就是需要 mock 某些包含 final、static 等方法的类时使用,使用方法:@PrepareForTest({System.class, LogUtils.class}),在方法中有new对象的时候也需要在@PrepareForTest中声明。
@PowerMockIgnore
PowerMock 是使用自定义的类加载器来加载被修改过的类,从而达到打桩的目的。@PowerMockIgnore 注解告诉 PowerMock 忽略哪些包下的类,从而消除类加载器引入的 ClassCastException。使用方法:@PowerMockIgnore({“javax.management.”, “javax.net.ssl.”, “javax.script.*”})
@SuppressStaticInitializationFor
告诉 PowerMock 哪些包下的类需要被抑制静态初始化,包括 static 代码块或者 static 变量的初始化。防止因静态初始化导致的错误。使用方法:
@SuppressStaticInitializationFor({“com.xxx.SmsServiceImpl”})
@Mock
mock 待测类的普通属性,最常见的就是通过 Spring @Autowired 自动注入的 bean。这类属性并非 final,也并非 static,只需要在测试类中使用 @Mock 注解,Pwermock 框架就能自动生成 mock 对象,并自动注入到 @InjectMock 修饰的待测类中。
也可以使用比较通用的 mock() 方法,调用 mock(XXX.class),能生成一个 mock 的 XXX 对象,然后通过反射获取待测类的字段,再将 mock 对象赋给字段。一般用于不是由 Spring @Autowired 注入的普通属性。