本来打算是把Spock的使用写成一篇的,后来发现太长了而且结构比较冗杂,还是拆分出来比较好,而且这样也比较好检索。初次使用,如果有误,请轻喷,并指出问题。我会及时纠正。
要使用Spock,必须要有Junit和Mock的基本概念和使用。建议在使用Spock之前先懂了解一下Junit和Mockitio
上一篇博客:《SpringBoot+Spock的熟悉之路(一):能正常启动并使用Spock》
下一篇博客:《SpringBoot+Spock的熟悉之路(三):用Spock对SpringBoot进行单元测试》
一定要注意版本的问题!一定要注意版本的问题!一定要注意版本的问题!
Tool | Version |
---|---|
Intellij IDEA | 2018.3 Ultimate |
SpringBoot | 2.0.1 |
Java | 1.8 |
mybatis-spring-boot | 2.0.1 |
Groovy | 2.4.6 |
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
<!--服务器配置的是Oracle-->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc8</artifactId>
<version>12.2.0.1</version>
</dependency>
<!---------------Spock必须的------------------->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>1.3-groovy-2.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<version>1.3-RC1-groovy-2.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.6</version>
</dependency>
<!-------------- 可选项 ----------------->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency> <!-- 允许MockInterface等 -->
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.9.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.objenesis</groupId>
<artifactId>objenesis</artifactId>
<version>2.6</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
本篇并不会着重讲Mockito的基本概念和所有用法,想了解请参考专门讲解Mockito的博客
我们代码有时候不可避免的会与其他模块或者其他层产生依赖,比如我们的Controller里一般会自动@Autowired
一个Service,而我们的项目一般还有BaseController,BaseController里还会@Autowired
一些我们自己的工具类比如ExcelUtils等,这些东西不一定会在我们所有的Controller里面都用到,我们也不想在每个Controller测试里都去处理BaseController中的依赖问题。为了解决这种情景,Mock应运而生,而Mockito只是java使用Mock的其中一个框架。
注意,这一个模块下面的写法为Mockitio的,不是Spock的
这三个是Mockito框架中的三种基本行为
package com.example.demo.entity;
import lombok.Data;
@Data
public class DemoEntity {
private Integer number;
private String str;
public int method1(){
return 1;
}
public String method2(){
return "hello";
}
}
作用为可以概括为把被Mock的对象里的成员变量以及设为Null或者0,并将方法设为空的方法体
package com.example.demo.simple
import com.example.demo.entity.DemoEntity
import org.mockito.Mockito
import spock.lang.Shared
import spock.lang.Specification
class MockSpec extends Specification {
@Shared
DemoEntity demoEntity
def setup(){
}
def "mock test"(){
given:
demoEntity = Mockito.mock(DemoEntity.class)
expect:
demoEntity.method1() == 0
demoEntity.str == null
}
}
执行后是
可以看出来,Mock会将一个类里的需要赋值的变量或者方法赋为0或者null
当我们需要被mock对象里的某些方法,并且希望这些方法以我们期望的值进行返回时,可以对这些方法进行stub,来规定它的返回值。也可以理解为重新定义一个方法的返回值。
def "stub test"(){
given:
demoEntity = Mockito.mock(DemoEntity.class)
//Stub往往和Mock一起行动。相当于在这里定义method1的行为
//这行代码的意思是,命令demoEntity的method1方法被调用时返回5。这种重新定义的行为叫做stub
Mockito.when(demoEntity.method1()).thenReturn(5)
expect:
demoEntity.method1() == 5
demoEntity.method2() == null
}
执行后是
可以看出来原来的方法已经被Stub过后的行为覆盖,而没有进行定义的方法依旧为空
不同于Mock和Stub可以在interface和abstract class起作用,Spy只能作用在一个实体类。
概括起来就是,Spy会覆盖被Stub的方法而保留没有被stub的实际的方法。
def "spy test"(){
given:
demoEntity = Mockito.spy(DemoEntity.class)
Mockito.when(demoEntity.method1()).thenReturn(5)
expect:
demoEntity.method1() == 5
demoEntity.method2() == "hello"
}
执行过后,
可以看出来,没有被Stub的方法使用的是原来自己定义的行为
Spock本身就支持三种基本行为,并且有自己的写法
DemoEntity demoEntity1 = Mock() //或者 def classa = Mock(ClassA)
DemoEntity demoEntity2 = Stub() // 或者 def classb = Stub(ClassB)
DemoEntity demoEntity3 = Spy() //或者 def classc = Spy(ClassC)
当然也可以定义和stub混合的写,例如
DemoEntity demoEntity1 = Mock(){
demoEntity1 .method1() >> 5
//这种写法是Spock的语法优势之一
//这里可以理解为,规定classa里的method1方法被调用后返回一个叫“hello”的字符串
}
如果想同时享受groovy的闭包优势和编辑器的代码提示,也可以这么写
DemoEntity demoEntity1 = Mock(DemoEntity ){
method1() >> 5
}
那么Mock()对象和Stub()对象有什么区别呢?
按照Spock官方的说法,Stub()对象不关心method被执行了多少次,只关心返回值。这么说有点晦涩难懂,我举个例子
class SpockMockSpec extends Specification{
DemoEntity demoEntity1 = Mock(DemoEntity ){
method1() >> 5
}
def"number of call test"(){
given:
//TODO:给一些初始值
when:
//TODO:执行一些操作,让demoEntity1的method1方法被调用两次
demoEntity1.method1()
demoEntity1.method1()
then:
//Spock语法,意思是判断classa中的method1方法是否被调用了两侧
2 * demoEntity1.method1()
}
}
如果是这么写,那么这个测试是通过的,因为我们确实在程序中调用了两次classa.method1()
但是如果将Mock那块的写法换成了
DemoEntity demoEntity1 = Stub(DemoEntity){
method1() >> 5
}
在执行后,控制台会报错
org.spockframework.runtime.InvalidSpecException: Stub 'demoEntity1' matches the following required interaction:
2 * demoEntity1.method1() (0 invocations)
Remove the cardinality (e.g. '1 *'), or turn the stub into a mock.
意思很明显,用Stub定义的对象无法进行调用次数判断,如果要使用该功能请将stub换成mock
在Spock中可以用将自己的Mock()方法和Mockito的@MockBean注解混合使用。但我个人建议请谨慎使用。在使用Mocktio的注解之前,一定要先查一下Spock是否支持Mockito的这个注解,一定要清晰的认识一点:
Spock不是Mocktio。比如处理依赖mock的时候,Mocktio有专门的@InjectMocks和@Mock组合,而Spock并不支持该写法,需要进入第三方组件。这个在之后写单元测试的博客的时候会做到讲解
Spock完整语法请看官方文档中的3,4,5,6部分。其中第5部分与Mockitio的对比比较多
Spock | Mockito |
---|---|
Mock() | @MockBean |
Stub() | @StubBean |
Spy() | @SpyBean |
classa.method1(_ as List) >> “5” | Mockito.when(classa.method2(Mockito.anyList())).thenReturn(“5”) |
list.size() == 2 | Assert.equals(list.size(),2) |
2 * list.add(“a”) | Mockito.verify(list, Mockito.times(2)).add(“a”); |
(1…_) * list.add(“a”) | Mockito.verify(list, Mockito.atLeastOnce()).add(“a”); |
@Subject and @Collaborator | @InjectMocks and @Mock |
本模块主要是把自己踩过的一些比较大的坑给单独列举出来
详细代码可以看我的下一篇博客。Spock并不支持@InjectMocks和@Mock的组合,要使用对应的功能可以引入Mockitio为Spock专门开发的第三方工具。
总之一句话,一定要搞清楚,虽然Spock中可以使用Mockitio的功能,但Spock终究不是Mockitio,在使用Mockitio的注解之前一定要搞清楚Spock是否支持此功能
Spock in Java 慢慢爱上写单元测试
spock-testing-exceptions-with-data-tables
Spock官方文档
Spock开源GitHub
Difference between Mock / Stub / Spy in Spock test framework
Mocks Aren’t Stubs
@Mock/@InjectMocks for groovy - spock