关于SpringBoot框架下的service层单元测试问题(mockito)
mockito的官方文档:
关于Junit测试业务逻辑层中出现的【方法的输入输出没有规范、测试高度依赖spring boot上下文环境、测试用例不完整等】这些问题,我们使用更完整的测试方法来解决。
学习原因:
针对最近遇到的问题:在SpringBoot框架下,如何脱离Spring的环境进行service层的单元测试,同时面临着【方法的输入输出没有规范、测试高度依赖spring boot上下文环境、测试用例不完整等】这些问题,查了很多资料之后,发现mockito可以很好的解决我当前遇到的问题。因为在这个过程中,查询的资料由于使用的mockito的版本不一致,还有写代码使用的框架不一致,导致在学习和实践测试的过程中走了很多弯路。现在总结如下,提供大家使用,少走弯路。
当前我的代码框架是:SpringBoot下,采用SpringMVC三层架构模式,使用SpringDataJPA处理简化DAO层的编写,语言为kotlin。
一、单元测试的目标和挑战
单元测试的思路是在不涉及依赖关系的情况下测试代码(隔离性),所以测试代码与其他类或者系统的关系应该尽量被消除。一个可行的消除方法是替换掉依赖类(测试替换),也就是说我们可以使用替身来替换掉真正的依赖对象。
使用Mockito可以明显的简化对外部依赖的测试类的开发。
二、mockito细节讲解
2.1 基于Mockito的Mock测试
*对Mock object定义如下:
In object-oriented programming, mock objects are simulated objects that mimic the behavior of real objects in controlled ways.
Mock测试用虚拟的对象来代替真实对象来完成测试工作。为什么要用虚拟对象来代替真实对象?一是因为由于开发分工的问题,导致测试时真实的对象并不存在,二是因为真实对象的行为不可预知,三是可能真实对象难以创建,四是由于真实对象的响应可能很慢。
通常的单元测试仅仅测试方法的结果,Mock测试在此之上能够测试方法的行为,例如某个方法是否被调用或者某方法被调用的次数等。
三、mockito的使用步骤
Mockito是一个用于java程序的Mock测试框架,相对于easymock等Mock框架,采用Mockito框架的测试代码更加简洁,可读性更强。在pom文件中添加依赖既可使用。
3.1 添加mockito的maven依赖
需要在 Maven 声明依赖,你可以在 http://search.maven.org 网站中搜索
g:”org.mockito”, a:”mockito-core” 来得到具体的声明方式。
下面是2.0.2版本的mockito:
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-all -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>2.0.2-beta</version>
<scope>test</scope>
</dependency>
3.2 测试依赖环境的定义
目前为止,有两种方法可以初始化 fields:
(1)Mockito 现在提供一个 JUnit rule。 使用 Mockito 提供的注解比如 @Mock, @Spy,
@InjectMocks 等等。
(2)用 @RunWith(@MockitoJUnitRunner.class) 标注 JUnit 测试类
在 @Before 之前调用 MockitoAnnotations.initMocks(Object)
现在你可以选择使用一个 rule:
@RunWith(@MockitoJUnitRunner.class)
public class TheTest {
@Rule public MockitoRule mockito = MockitoJUnit.rule();
// ...
}
3.3 使用mockito创建和配置mock对象
@mock为一个interface提供一个虚拟的实现。
@InjectMocks将本test类中的mock(或@mock)注入到被标注的对象中去,也就是说被标注的对象中需要使用标注了mock(或@mock)的对象。
mockito遇到使用注解的字段会调用MockitoAnnotations.initMocks(this) 来初始化该 mock 对象。另外也可以通过使用@RunWith(MockitoJUnitRunner.class)来达到相同的效果。
代码见3.4.3
3.4 在测试方法中配置mock
使用测试桩stub来定义在service的实现代码中使用到的Repository代码的设定返回值:
3.4.1 DAO层
interface ProjectRepository : JpaRepository<Project, tag_t> {
fun findByProjectId(projectId: String): Project
}
//JpaRepository是SpringDataJPA提供的一个类,里面有自定义的方法,我们集成之后直接调用即可,这个不重要,可以直接跳过不要看
@NoRepositoryBean
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
List<T> findAll();
List<T> findAll(Sort var1);
List<T> findAllById(Iterable<ID> var1);
<S extends T> List<S> saveAll(Iterable<S> var1);
void flush();
<S extends T> S saveAndFlush(S var1);
void deleteInBatch(Iterable<T> var1);
void deleteAllInBatch();
T getOne(ID var1);
<S extends T> List<S> findAll(Example<S> var1);
<S extends T> List<S> findAll(Example<S> var1, Sort var2);
}
3.4.2 service层的实现
//service接口
interface AaaService{
fun create(a:Aaa):Aaa
}
//service层的实现
//注入Aaa类的DAO层
@resource private lateinit var aRepository:AaaRepository
class AaaServiceImpl:Aaa{
override fun create(a:Aaa):Aaa{
return aRepository.save(a)
}
}
3.4.3 测试类代码:
@RunWith(MockitoJUnitRunner::class)
class AaaServiceImplTest {
//用于定义被Mock的组件
@Mock private lateinit var aaaRepository: AaaRepository
//mock一个要测试的类对象,同时@Mock注解的会被依赖注入到@InjectMocks注解的类对象中
@InjectMocks
private lateinit var aaaService: ProjectServiceImpl
private lateinit var aaa:Aaa
@Before
fun setUp() {
//用于初始化@Mock注解修饰的组件
MockitoAnnotations.initMocks(this)
//定义类对象
aaa = Aaa()
}
@Test
fun create() {
//这是自定义的一个测试桩stub,定义在service层关于dao层语句的返回值定义,使得service代码的测试脱离开dao层。
Mockito.`when`(aaaRepository.save(aaa)).thenReturn(aaa)
//运行方法
val result = aaaService.create(aaa)
//这里可以多验证几种结果
assertEquals(aaa, result)
assertEquals(projectInfo.projectId, result.projectId)
//判断某个方法是否被调用(是否发生交互)
Mockito.verify(aaaRepository).save(aaa)
}
}
至此,mockito的测试步骤就完了,更多的小的杂乱的知识点,在上面给出的网址中。下面我们来看看在测试类中对测试更全面的介绍。
四、多项测试简介
下面结合Mockito工具的使用来谈谈Mock中涉及一些重要概念。
Stub:
A stub is a controllable replacement for an existing dependency (or collaborator) in the system. By using a stub, you can test your code without dealing with the dependency directly.
我的理解,Stub用于绕开实际依赖或者某些实际方法的执行。可以用Stub去伪造一个方法来绕过数据库访问方法的执行。在Mockito中提供了when语句来实现Stub。例如when(a.func()).thenReturn(1)伪造执行a.func(),当调用a.func()时返回值定义为1。
Behavior verification:
Mock测试区别于一般单元测试方法的重要特点是Mock测试可以进行行为验证。在mockito中采用verify(a, times(n)).func()来验证对象a的func()方法是否被调用n次。
Wrap a real object:
Mock对象只能调用Stub方法,而不能调用其真实方法,否则会抛出空指针异常。而Mockito提供了spy机制可以用于监控一个真实对象,此时可以调用该对象的真实方法。在Mockito中可以采用@Spy标签或者调用Mockito.spy(T object)方法。
Mock中常用注解有:
- @Mock:用于标识mock对象。
- @InjectMocks:将用@Mock标注的mock对象,注入到被某个被该注解标注的测试的对象中。
- @Spy:用来标注某个被@Mockito标注的真实对象。
4.2 mockito的限制
Mockito当然也有一定的限制。而下面三种数据类型则不能够被测试
final classes
anonymous classes
primitive types
doReturn().when()和when().thenReturn()的区别。后者会调用对象的真实api,而前者遇到函数调用直接返回doReturn中设置的值。
参考:参考的部分内容
更详细的内容最好是参考:mockito的官方文档
下一篇: React中useRef的具体使用