Spock,Mock和Mockitio的關係
前言
本來打算是把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>
Spock和Mockito
本篇並不會着重講Mockito的基本概念和所有用法,想了解請參考專門講解Mockito的博客
Mockito的簡述
我們代碼有時候不可避免的會與其他模塊或者其他層產生依賴,比如我們的Controller裏一般會自動@Autowired
一個Service,而我們的項目一般還有BaseController,BaseController裏還會@Autowired
一些我們自己的工具類比如ExcelUtils等,這些東西不一定會在我們所有的Controller裏面都用到,我們也不想在每個Controller測試裏都去處理BaseController中的依賴問題。爲了解決這種情景,Mock應運而生,而Mockito只是java使用Mock的其中一個框架。
Mock,Stub和Spy
注意,這一個模塊下面的寫法爲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
作用爲可以概括爲把被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
Stub
當我們需要被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過後的行爲覆蓋,而沒有進行定義的方法依舊爲空
Spy
不同於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中的Mock,Stub和Spy
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 |
遇到的問題
本模塊主要是把自己踩過的一些比較大的坑給單獨列舉出來
@InjectMocks在Spock中失效的問題
詳細代碼可以看我的下一篇博客。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