Java中Spock框架Mock对象的方法经验总结
作者:FunTester
前言:
下面分享一些使用过的一个常用项目,部分信息隐去了。大家在自己项目中实践的时候可以参考,尽量别直接抄代码,我自己使用过程中有很多兼容性的坑,特别是IDE自动import
功能。
一、技术方案
本技术方案基于公司力推的Spock
单元测试框架,spock
是一款基于Groovy语言的单元测试框架,其基础也是Java的Junit,目前最新版已经到了2.0,但对Groovy和相应的Java版本要求较高,所以Groovy版本使用1.+,Spock自带的Mock和Spy足够好了,对于对象行为的模拟满足绝大部分场景,但是涉及静态方法模拟时候存在局限性,所以引入Mockito
和PowerMock
来实现设计静态方法的测试模拟场景。
以下为相关依赖版本:
<dependency> <groupId>org.spockframework</groupId> <artifactId>spock-core</artifactId> <version>1.2-groovy-2.5</version> <scope>test</scope> </dependency> <dependency> <groupId>org.spockframework</groupId> <artifactId>spock-spring</artifactId> <version>1.2-groovy-2.5</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito2</artifactId> <version>1.7.4</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>1.7.4</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>2.8.9</version> <scope>test</scope> </dependency>
因为Spock
本身需要Groovy
语言支持,所以也需要一个Groovy-all的依赖,请注意注自带的注释:
<dependency> <!-- use a specific Groovy version rather than the one specified by spock-core --> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy-all</artifactId> <version>2.4.7</version> </dependency>
另外,提供的配置文件中多了几项特殊场景下使用的依赖,提供参考:
<dependency> <!-- enables mocking of classes (in addition to interfaces) --> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy</artifactId> <version>1.9.9</version> <scope>test</scope> </dependency> <dependency> <!-- enables mocking of classes without default constructor (together with CGLIB) --> <groupId>org.objenesis</groupId> <artifactId>objenesis</artifactId> <version>3.0.1</version> <scope>test</scope> </dependency> <dependency> <!-- only required if Hamcrest matchers are used --> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-core</artifactId> <version>2.1</version> <scope>test</scope> </dependency>
二、非静态资源
由于多个单测框架的方法名重复较多,我把import
内容也贴出来了,如果同样的代码无法运行,可以排查一下是否import
正确的方法和类。这里不是很建议import static
,因为可能出现混用以及不易排查的问题。
由于目前测试中没有遇到使用Spy放行的逻辑,所以均使用Mock模式,需要对Mock对象的方法进行模拟。这个分为两类:Spock和PowerMock
(结合Mockito)。原因是在混合静态资源和非静态资源场景下,指定了PowerMock
的@RunWith
运行规则,不兼容Spock写法,需要用到PowerMock
框架Mock对象的功能。
三、Mock被测对象
1.@Autowired构造方法
用一个controller举例,源代码如下:
@Api(tags = "SLA规则管理模块") @Slf4j @RestController @RequestMapping("/hickwall/v1/static/sla") public class FunController { HttpServletRequest request; ISlaService service; @Autowired public FunController(HttpServletRequest request, ISlaService service) { this.request = request; this.service = service; } }
Spock单测代码如下:
import com.funtester.service.ISlaService import com.funtester.vo.sla.SlaBean import spock.lang.Shared import spock.lang.Specification import javax.servlet.http.HttpServletRequest class FunControllerTest extends Specification { def service = Mock(ISlaService) @Shared def request = Mock(HttpServletRequest) def FunController = new FunController(request, service) }
2.@Autowired属性对象,无构造方法
源代码如下:
public class ApiImpl implements IApi { @Autowired private ApiRMapper mapper; }
Spock单测部分代码如下:
import com.funtester.mapper.ApiRMapper import com.funtester.vo.ApiR import spock.lang.Shared import spock.lang.Specification ApiRMapper mapper = Mock(ApiRMapper) def drive = new ApiImpl(mapper:mapper)
3.PowerMock用法
场景也分为两种:有无构造方法,除了Mock方法不同以外,其他均相同,这里不列举。
PS:如果对象属性中有未被@Autowired注释的属性,不能用@AllArgsConstructor的lombok注解,服务启动会报错。
源代码如下:
@Component @Slf4j public class TaskScheduled { @Autowired IService service; @Value("${hickwall.statistic.cid}") public String cid; }
4.共享对象以及初始化
统一使用Spock提供的功能,用到的注解@Shared,不加的话无法在Spock方法中进行赋值操作,但是可以当做一个普通的对象使用。
Spock框架Demo:
@Shared def slaBean = new SlaBean() def setupSpec() { request.getHeader("operator") >> "FunTester" slaBean.name = "测试" slaBean.quota = 1 slaBean.upstream = "PRO" slaBean.threshold = 0.1 slaBean.creator = "FunTester" slaBean.id = 100 }
四、定义对象行为
1.Spock定义Mock对象行为
基础的Spock语法结构when-then-expct如下:
def "AddSla"() { when: def sla = FunController.addSla(slaBean) then: service.addSla(_) >> {f -> assert "FunTester" in f.creator 1 } expect: sla.code == 0 sla.data == 1 }
也可以在最开始加上given
,when
和then
通常一起使用。
上述Demo
在Mock方法的时候对参数进行了断言和处理,这也是Spock框架的一个特性,其他均为Groovy
语法特性。
其他定义Mock行为的语法如下:
service.getAllGroup(_,_) >> null//返回null service.getAllGroup(_,_) >> {throw new Exception()} //抛出异常 service.getAllGroup(_,_) >> []//返回空list,Groovy默认实现ArrayList service.getAllGroup(_,_) >> [slaBean,slaBean]//返回正常list service.getAllGroup(_,_) >> [slaBean,slaBean]//返回正常list service.getAllGroup(_,10) >> [slaBean,slaBean]//定时某个参数 service.getAllGroup(any(),10) >> [slaBean,slaBean]//any()等效于_ service.getAllGroup(any(),10) >> service.getAllGroup(1,10)//调用其他方法返回
2.Mockito模拟对象行为
Mockito
和PowerMock
配合使用语法稍微复杂一些。首先我们需要先定义对象行为(通常在com.funtesterbase.task.TaskScheduledTest#setupSpec
方法中),然后在用例用使用。
定时对象行为:
Mockito.when(newutil.filter(Mockito.any())).thenReturn(true)
定义行为以后,就可以在Spock用例中正常使用,包括在通过Mock对象创建的对象方法中,如果调用到定义过行为的方法,也会走自定义的逻辑。
其他常用定义行为:
Mockito.when(newutil.filter(Mockito.any())).thenReturn(null) Mockito.when(newutil.filter(Mockito.any())).thenThrow(Exception.class)//抛出异常 PowerMockito.doNothing().when(newutil).filter(Mockito.any(ArrayList.class))//dothing,什么都不做
第三个例子中我们假设filter
方法是一个无返回的void
方法。
通常我们需要构建返回对象,如果对象需要赋值的属性过多,可以使用初始化赋值的方法,下面是Mock一个返回list的方法返回值的Demo:
Mockito.when(newser.selectAllService()).thenReturn([new NewInterface() { { setUrl("/abc") setNname("test TGFC z;/./") setMethod("GET") } }, new NewInterface() { { setUrl("/abcd") setNname("test") setMethod("POST") } }, new NewInterface() { { setUrl("/abce") setNname("test") setMethod("GET") } }])
到此这篇关于Spock框架Mock对象的方法经验总结的文章就介绍到这了,更多相关Spock框架Mock对象内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!