您需要開始的只是為了在MockK庫中添加依賴項。
| 方法 | 操作說明 |
|---|---|
![]() | tistimplation“ io.mockk:Mockk:$ {mockkversion}”
|
(Kotlin DSL) | tistimplementation(“ io.mockk:mockk:$ {mockkversion}”) |
![]() | <依賴項>
<groupId> io.mockk </groupId>
<Artifactid> MOCKK-JVM </artifactid>
<版本> $ {mockkversion} </version>
<Scope>測試</scope>
</dependency>
|
tistimpletation“ io.mockk:mockk-android:$ {mockkversion}”
tistimpletation“ io.mockk:Mockk-Agent:$ {MOCKKVERSION}”
| |
AndroidTestImplementation“ io.mockk:Mockk-android:$ {mockkversion}”
AndroidTestImplementation“ io.mockk:Mockk-agent:$ {mockkversion}”
|
最簡單的示例。默認情況下,模擬是嚴格的,因此您需要提供一些行為。
val car = mockk< Car >()
every { car.drive( Direction . NORTH ) } returns Outcome . OK
car.drive( Direction . NORTH ) // returns OK
verify { car.drive( Direction . NORTH ) }
confirmVerified(car)有關更詳細的示例,請參見下面的“功能”部分。
從1.13.0版本中,Mockk支持Kotlin 1.4及更高版本
mockkStatic在JDK 16+上可能無法使用; InaccessibleObjectException / IllegalAccessException :在此處閱讀更多目錄:
檢查KT的一系列文章“嘲笑不是火箭科學”。從嘲笑所有高級功能的描述的基本知識中描述了Mockk的學院。
基礎
預期行為和行為驗證
MOCKK功能
Mockk高級功能
與Kotlin,Junit和Mockk一起測試Quarkus
揭開Mockk的黑魔法(EN,翻譯)
Mockk指南
Marco Cattaneo的“ Kotlin單位測試”
(視頻)使用Mockk中的驗證來驗證模擬對像上的函數調用
在raywenderlich.com上使用Mockk付費課程進行測試
TDD for Android視頻教程第1部分,第2部分Ryan Kay
(視頻)Android開發人員實時編碼#13:使用Mockk,Coroutines,測試驅動開發的單元測試
Kotlinconf 2018-菲利普·豪爾(Philipp Hauer)
kotlin-fullstack樣本使用涵蓋測試的Mockk項目
Dzone文章
Habrahabr文章(RU)
與Mockk一起在Kotlin嘲笑-Yannick de turck
您可以使用註釋簡化模擬對象的創建:
class TrafficSystem {
lateinit var car1 : Car
lateinit var car2 : Car
lateinit var car3 : Car
}
class CarTest {
@MockK
lateinit var car1 : Car
@RelaxedMockK
lateinit var car2 : Car
@MockK(relaxUnitFun = true )
lateinit var car3 : Car
@SpyK
var car4 = Car ()
@InjectMockKs
var trafficSystem = TrafficSystem ()
@Before
fun setUp () = MockKAnnotations . init ( this , relaxUnitFun = true ) // turn relaxUnitFun on for all mocks
@Test
fun calculateAddsValues1 () {
// ... use car1, car2, car3 and car4
}
}注射首先嘗試按名稱匹配屬性,然後按類或超類匹配。檢查自定義的lookupType參數。
即使應用了private ,也會注入屬性。從最大的參數到最低,選擇了注射的構造函數。
@InjectMockKs默認情況下僅注入未分配的lateinit var s或var s。要更改此操作,請使用overrideValues = true 。即使該值已經以某種方式初始化,這也會分配該值。要注入val s,請使用injectImmutable = true 。對於較短的表示法,請使用@OverrideMockKs ,默認情況下與@InjectMockKs相同,但打開這兩個標誌。
Junit 4公開了基於規則的API,以允許在測試生命週期後進行一些自動化。 Mockk包括一個規則,該規則使用此規則來設置和拆除您的模擬,而無需手動調用MockKAnnotations.init(this) 。例子:
class CarTest {
@get:Rule
val mockkRule = MockKRule ( this )
@MockK
lateinit var car1 : Car
@RelaxedMockK
lateinit var car2 : Car
@Test
fun something () {
every { car1.drive() } just runs
every { car2.changeGear(any()) } returns true
// etc
}
}在Junit5中,您可以使用MockKExtension來初始化模擬。
@ExtendWith( MockKExtension :: class )
class CarTest {
@MockK
lateinit var car1 : Car
@RelaxedMockK
lateinit var car2 : Car
@MockK(relaxUnitFun = true )
lateinit var car3 : Car
@SpyK
var car4 = Car ()
@Test
fun calculateAddsValues1 () {
// ... use car1, car2, car3 and car4
}
}此外,它增加了在測試功能參數上使用@MockK和@RelaxedMockK的可能性:
@Test
fun calculateAddsValues1 (@MockK car1 : Car , @RelaxedMockK car2 : Car ) {
// ... use car1 and car2
}最後,此擴展程序將在@AfterAll回調中調用unmockkAll和clearAllMocks ,以確保在每個測試類執行後確保您的測試環境乾淨。您可以通過將@MockKExtension.KeepMocks註釋在您的課程或全球範圍內添加mockk.junit.extension.keepmocks=true屬性來禁用此行為。 (因為v1.13.11)或者,由於默認情況下的clearAllMocks ( currentThreadOnly=false )不是線程@AfterAll ,如果您需要並行運行測試,則可以在類中添加MockKExtension.RequireParallelTesting enotation mockk.junit.extension.requireParallelTesting=true如果明確調用了clearAllMocks ,則可以提供clearAllMocks(currentThreadOnly = true) ,以便它僅清除同一線程中創建的模擬(因為v1.13.12)。
您可以通過@MockKExtension.ConfirmVerification確保實際驗證所有固執方法。
這將在每次測試後的所有模擬中都在內部confirmVerified ,以確保沒有不必要的固執。
請注意,在IDE中運行測試時,這種行為可能無法正常工作,因為Gradle負責處理這些confirmVerified通話失敗時要拋出的例外。
您可以通過@MockKExtension.CheckUnnecessaryStub來註釋測試類,確保所有固執方法至少使用一次 - 至少使用一次。
這將在每次測試之後的所有模擬上都在內部致電checkUnnecessaryStub ,以確保沒有不必要的固執。
間諜允許您混合模擬和真實對象。
val car = spyk( Car ()) // or spyk<Car>() to call the default constructor
car.drive( Direction . NORTH ) // returns whatever the real function of Car returns
verify { car.drive( Direction . NORTH ) }
confirmVerified(car)注意1:間諜對像是傳遞對象的副本。注2:如果使用具有懸浮功能的間諜,則存在一個已知問題:#554
relaxed mock是一個模擬,它為所有功能返回一些簡單的值。這使您可以跳過每種情況的指定行為,同時仍然使所需的事情保持不動。對於參考類型,返回鍊式模擬。
val car = mockk< Car >(relaxed = true )
car.drive( Direction . NORTH ) // returns null
verify { car.drive( Direction . NORTH ) }
confirmVerified(car)注意:放鬆的模擬與通用返回類型的工作不佳。在這種情況下,通常會拋出類別的例外。在通用返回類型的情況下,請選擇手動固定。
解決方法:
val func = mockk < () -> Car > (relaxed = true ) // in this case invoke function has generic return type
// this line is workaround, without it the relaxed mock would throw a class cast exception on the next line
every { func() } returns Car () // or you can return mockk() for example
func()有時,您需要使某些功能固執,但仍然對他人或特定參數調用真實方法。通過將callOriginal()傳遞給answers ,這是可能的,該答案既適用於放鬆和非釋放模擬的模擬。
class Adder {
fun addOne ( num : Int ) = num + 1
}
val adder = mockk< Adder >()
every { adder.addOne(any()) } returns - 1
every { adder.addOne( 3 ) } answers { callOriginal() }
assertEquals( - 1 , adder.addOne( 2 ))
assertEquals( 4 , adder.addOne( 3 )) // original function is called如果要放鬆Unit - 返回函數,則可以使用relaxUnitFun = true作為mockk函數, @MockK Mockk註釋或MockKAnnotations.init函數的參數。
功能:
mockk< ClassBeingMocked >(relaxUnitFun = true )註解:
@MockK(relaxUnitFun = true )
lateinit var mock1 : ClassBeingMocked
init {
MockKAnnotations . init ( this )
}無用的坎諾特。
@MockK
lateinit var mock2 : ClassBeingMocked
init {
MockKAnnotations . init ( this , relaxUnitFun = true )
}對象可以通過以下方式變成模擬:
object ObjBeingMocked {
fun add ( a : Int , b : Int ) = a + b
}
mockkObject( ObjBeingMocked ) // applies mocking to an Object
assertEquals( 3 , ObjBeingMocked .add( 1 , 2 ))
every { ObjBeingMocked .add( 1 , 2 ) } returns 55
assertEquals( 55 , ObjBeingMocked .add( 1 , 2 ))要恢復後退,請使用unmockkObject或unmockkAll (更具破壞性:取消對象,靜態和構造函數模擬)
@Before
fun beforeTests () {
mockkObject( ObjBeingMocked )
every { ObjBeingMocked .add( 1 , 2 ) } returns 55
}
@Test
fun willUseMockBehaviour () {
assertEquals( 55 , ObjBeingMocked .add( 1 , 2 ))
}
@After
fun afterTests () {
unmockkObject( ObjBeingMocked )
// or unmockkAll()
}儘管有Kotlin語言限制,但您可以通過測試邏輯來創建對象的新實例:
val newObjectMock = mockk< ObjBeingMocked >()有時您需要對任意班級的模擬。在這種情況下,請使用mockkClass 。
val car = mockkClass( Car :: class )
every { car.drive( Direction . NORTH ) } returns Outcome . OK
car.drive( Direction . NORTH ) // returns OK
verify { car.drive( Direction . NORTH ) }可以使用mockkObject模擬枚舉:
enum class Enumeration ( val goodInt : Int ) {
CONSTANT ( 35 ),
OTHER_CONSTANT ( 45 );
}
mockkObject( Enumeration . CONSTANT )
every { Enumeration . CONSTANT .goodInt } returns 42
assertEquals( 42 , Enumeration . CONSTANT .goodInt)有時,尤其是在您不擁有的代碼中,您需要模擬新創建的對象。為此,提供了以下結構:
class MockCls {
fun add ( a : Int , b : Int ) = a + b
}
mockkConstructor( MockCls :: class )
every { anyConstructed< MockCls >().add( 1 , 2 ) } returns 4
assertEquals( 4 , MockCls ().add( 1 , 2 )) // note new object is created
verify { anyConstructed< MockCls >().add( 1 , 2 ) }基本思想是,在執行模擬類的構造函數(其中任何一個)之後,對象成為一個constructed mock 。
這種模擬的模擬行為連接到由anyConstructed<MockCls>()表示的特殊prototype mock 。
每類此類prototype mock都有一個實例。呼叫記錄也發生在prototype mock中。
如果未指定該函數的行為,則執行原始功能。
如果一堂課有多個構造函數,則可以單獨嘲笑:
class MockCls ( private val a : Int = 0 ) {
constructor (x : String ) : this (x.toInt())
fun add ( b : Int ) = a + b
}
mockkConstructor( MockCls :: class )
every { constructedWith< MockCls >().add( 1 ) } returns 2
every {
constructedWith< MockCls >( OfTypeMatcher < String >( String :: class )).add( 2 ) // Mocks the constructor which takes a String
} returns 3
every {
constructedWith< MockCls >( EqMatcher ( 4 )).add(any()) // Mocks the constructor which takes an Int
} returns 4
assertEquals( 2 , MockCls ().add( 1 ))
assertEquals( 3 , MockCls ( " 2 " ).add( 2 ))
assertEquals( 4 , MockCls ( 4 ).add( 7 ))
verify {
constructedWith< MockCls >().add( 1 )
constructedWith< MockCls >( " 2 " ).add( 2 )
constructedWith< MockCls >( EqMatcher ( 4 )).add( 7 )
}請注意,在這種情況下,為傳遞給constructedWith每組參數匹配器創建了一個prototype mock 。
您可以混合常規參數和匹配者:
val car = mockk< Car >()
every {
car.recordTelemetry(
speed = more( 50 ),
direction = Direction . NORTH , // here eq() is used
lat = any(),
long = any()
)
} returns Outcome . RECORDED
car.recordTelemetry( 60 , Direction . NORTH , 51.1377382 , 17.0257142 )
verify { car.recordTelemetry( 60 , Direction . NORTH , 51.1377382 , 17.0257142 ) }
confirmVerified(car)您可以停止通話鏈:
val car = mockk< Car >()
every { car.door( DoorType . FRONT_LEFT ).windowState() } returns WindowState . UP
car.door( DoorType . FRONT_LEFT ) // returns chained mock for Door
car.door( DoorType . FRONT_LEFT ).windowState() // returns WindowState.UP
verify { car.door( DoorType . FRONT_LEFT ).windowState() }
confirmVerified(car)注意:如果函數的返回類型是通用的,則有關實際類型的信息已消失。
為了使鏈接通話起作用,需要其他信息。
大多數時候,該框架將捕獲鑄造異常並進行autohinting 。
在明確需要的情況下,請在下一個呼叫之前使用hint 。
every { obj.op2( 1 , 2 ).hint( Int :: class ).op1( 3 , 4 ) } returns 5從版本1.9.1模擬可以將其鏈接到層次結構中:
interface AddressBook {
val contacts : List < Contact >
}
interface Contact {
val name : String
val telephone : String
val address : Address
}
interface Address {
val city : String
val zip : String
}
val addressBook = mockk< AddressBook > {
every { contacts } returns listOf (
mockk {
every { name } returns " John "
every { telephone } returns " 123-456-789 "
every { address.city } returns " New-York "
every { address.zip } returns " 123-45 "
},
mockk {
every { name } returns " Alex "
every { telephone } returns " 789-456-123 "
every { address } returns mockk {
every { city } returns " Wroclaw "
every { zip } returns " 543-21 "
}
}
)
}您可以將論點捕獲到CapturingSlot或MutableList 。
CapturingSlot通常是通過出廠方法slot<T : Any?>()創建的,並且有可能捕獲不可且不可否認的類型。 MutableList旨在在測試過程中捕獲多個值。
enum class Direction { NORTH , SOUTH }
enum class RecordingOutcome { RECORDED }
enum class RoadType { HIGHWAY }
class Car {
fun recordTelemetry ( speed : Double , direction : Direction , roadType : RoadType ? ): RecordingOutcome {
TODO ( " not implement for showcase " )
}
}
val car = mockk< Car >()
// allow to capture parameter with non nullable type `Double`
val speedSlot = slot< Double >()
// allow to capture parameter with nullable type `RoadType`
val roadTypeSlot = slot< RoadType ?>()
val list = mutableListOf< Double >()
every {
car.recordTelemetry(
speed = capture(speedSlot), // makes mock match calls with any value for `speed` and record it in a slot
direction = Direction . NORTH , // makes mock and capturing only match calls with specific `direction`. Use `any()` to match calls with any `direction`
roadType = captureNullable(roadTypeSlot), // makes mock match calls with any value for `roadType` and record it in a slot
)
} answers {
println ( " Speed: ${speedSlot.captured} , roadType: ${roadTypeSlot.captured} " )
RecordingOutcome . RECORDED
}
every {
car.recordTelemetry(
speed = capture(list),
direction = Direction . SOUTH ,
roadType = captureNullable(roadTypeSlot),
)
} answers {
println ( " Speed: ${list} , roadType: ${roadTypeSlot.captured} " )
RecordingOutcome . RECORDED
}
car.recordTelemetry(speed = 15.0 , direction = Direction . NORTH , null ) // prints Speed: 15.0, roadType: null
car.recordTelemetry(speed = 16.0 , direction = Direction . SOUTH , RoadType . HIGHWAY ) // prints Speed: [16.0], roadType: HIGHWAY
verifyOrder {
car.recordTelemetry(speed = or ( 15.0 , 16.0 ), direction = any(), roadType = null )
car.recordTelemetry(speed = 16.0 , direction = any(), roadType = RoadType . HIGHWAY )
}
confirmVerified(car)您可以使用atLeast , atMost或exactly參數檢查呼叫計數:
val car = mockk< Car >(relaxed = true )
car.accelerate(fromSpeed = 10 , toSpeed = 20 )
car.accelerate(fromSpeed = 10 , toSpeed = 30 )
car.accelerate(fromSpeed = 20 , toSpeed = 30 )
// all pass
verify(atLeast = 3 ) { car.accelerate(allAny()) }
verify(atMost = 2 ) { car.accelerate(fromSpeed = 10 , toSpeed = or ( 20 , 30 )) }
verify(exactly = 1 ) { car.accelerate(fromSpeed = 10 , toSpeed = 20 ) }
verify(exactly = 0 ) { car.accelerate(fromSpeed = 30 , toSpeed = 10 ) } // means no calls were performed
confirmVerified(car)或者您可以使用verifyCount :
val car = mockk< Car >(relaxed = true )
car.accelerate(fromSpeed = 10 , toSpeed = 20 )
car.accelerate(fromSpeed = 10 , toSpeed = 30 )
car.accelerate(fromSpeed = 20 , toSpeed = 30 )
// all pass
verifyCount {
( 3 .. 5 ) * { car.accelerate(allAny(), allAny()) } // same as verify(atLeast = 3, atMost = 5) { car.accelerate(allAny(), allAny()) }
1 * { car.accelerate(fromSpeed = 10 , toSpeed = 20 ) } // same as verify(exactly = 1) { car.accelerate(fromSpeed = 10, toSpeed = 20) }
0 * { car.accelerate(fromSpeed = 30 , toSpeed = 10 ) } // same as verify(exactly = 0) { car.accelerate(fromSpeed = 30, toSpeed = 10) }
}
confirmVerified(car)verifyAll驗證所有呼叫是否在沒有檢查其訂單的情況下發生。verifySequence驗證調用是否在指定的序列中發生。verifyOrder驗證呼叫以特定順序發生的呼叫。wasNot Called驗證模擬(或模擬列表)根本沒有被調用。 class MockedClass {
fun sum ( a : Int , b : Int ) = a + b
}
val obj = mockk< MockedClass >()
val slot = slot< Int >()
every {
obj.sum(any(), capture(slot))
} answers {
1 + firstArg< Int >() + slot.captured
}
obj.sum( 1 , 2 ) // returns 4
obj.sum( 1 , 3 ) // returns 5
obj.sum( 2 , 2 ) // returns 5
verifyAll {
obj.sum( 1 , 3 )
obj.sum( 1 , 2 )
obj.sum( 2 , 2 )
}
verifySequence {
obj.sum( 1 , 2 )
obj.sum( 1 , 3 )
obj.sum( 2 , 2 )
}
verifyOrder {
obj.sum( 1 , 2 )
obj.sum( 2 , 2 )
}
val obj2 = mockk< MockedClass >()
val obj3 = mockk< MockedClass >()
verify {
listOf (obj2, obj3) wasNot Called
}
confirmVerified(obj)要仔細檢查所有調用通過verify...構造驗證,您可以使用confirmVerified :
confirmVerified(mock1, mock2)將其用於verifySequence和verifyAll並沒有多大意義,因為這些驗證方法已經詳盡介紹了所有通話。
如果剩下一些無驗證的呼叫,它將引發例外。
可以從此確認中排除某些電話,請檢查下一節以獲取更多詳細信息。
val car = mockk< Car >()
every { car.drive( Direction . NORTH ) } returns Outcome . OK
every { car.drive( Direction . SOUTH ) } returns Outcome . OK
car.drive( Direction . NORTH ) // returns OK
car.drive( Direction . SOUTH ) // returns OK
verify {
car.drive( Direction . SOUTH )
car.drive( Direction . NORTH )
}
confirmVerified(car) // makes sure all calls were covered with verification由於乾淨且可維護的測試代碼需要零不必要的代碼,因此您可以確保沒有不必要的存根。
checkUnnecessaryStub(mock1, mock2)如果在測試代碼未使用的模擬上有一些聲明的電話,則會引發例外。如果您聲明了一些真正不必要的存根,或者測試的代碼未調用預期的存根,則可能會發生這種情況。
為了將不重要的電話排除在記錄中,您可以使用excludeRecords :
excludeRecords { mock.operation(any(), 5 ) }所有匹配的呼叫都將被排除在錄製之外。如果您使用詳盡的驗證: verifyAll , verifySequence或confirmVerified 。
val car = mockk< Car >()
every { car.drive( Direction . NORTH ) } returns Outcome . OK
every { car.drive( Direction . SOUTH ) } returns Outcome . OK
excludeRecords { car.drive( Direction . SOUTH ) }
car.drive( Direction . NORTH ) // returns OK
car.drive( Direction . SOUTH ) // returns OK
verify {
car.drive( Direction . NORTH )
}
confirmVerified(car) // car.drive(Direction.SOUTH) was excluded, so confirmation is fine with only car.drive(Direction.NORTH)要驗證並發操作,您可以使用timeout = xxx :
mockk< MockCls > {
every { sum( 1 , 2 ) } returns 4
Thread {
Thread .sleep( 2000 )
sum( 1 , 2 )
}.start()
verify(timeout = 3000 ) { sum( 1 , 2 ) }
}這將等到以下兩個狀態中的一個:通過驗證或達到超時。
如果函數返回Unit ,則可以使用justRun構造:
class MockedClass {
fun sum ( a : Int , b : Int ): Unit {
println (a + b)
}
}
val obj = mockk< MockedClass >()
justRun { obj.sum(any(), 3 ) }
obj.sum( 1 , 1 )
obj.sum( 1 , 2 )
obj.sum( 1 , 3 )
verify {
obj.sum( 1 , 1 )
obj.sum( 1 , 2 )
obj.sum( 1 , 3 )
}編寫justRun { obj.sum(any(), 3) }的其他方法:
every { obj.sum(any(), 3) } just Runsevery { obj.sum(any(), 3) } returns Unitevery { obj.sum(any(), 3) } answers { Unit }要模擬Coroutines,您需要在支持庫中添加另一個依賴關係。
| gradle |
|---|
tistimplation“ org.jetbrains.kotlinx:kotlinx-coroutines core:xx” |
| 小牛 |
|---|
<依賴項>
<groupId> org.jetbrains.kotlinx </groupId>
<Artifactid> kotlinx-coroutines-core </artifactid>
<版本> xx </version>
<Scope>測試</scope>
</dependency> |
然後,您可以使用coEvery , coVerify , coMatch , coAssert , coRun , coAnswers或coInvoke來模擬懸掛功能。
val car = mockk< Car >()
coEvery { car.drive( Direction . NORTH ) } returns Outcome . OK
car.drive( Direction . NORTH ) // returns OK
coVerify { car.drive( Direction . NORTH ) }為了模擬永不返回的suspend函數,您可以使用coJustAwait :
runTest {
val car = mockk< Car >()
coJustAwait { car.drive(any()) } // car.drive(...) will never return
val job = launch( UnconfinedTestDispatcher ()) {
car.drive( Direction . NORTH )
}
coVerify { car.drive( Direction . NORTH ) }
job.cancelAndJoin() // Don't forget to cancel the job
}注意:如果使用具有懸浮功能的間諜,則有一個已知問題:#554
Kotlin使您可以聲明不屬於任何類或對象的功能,稱為頂級函數。這些調用被轉化為jvm環境中的靜態方法,並生成特殊的Java類以保持功能。這些頂級功能可以使用mockkStatic模擬。您只需要導入該函數並將參考作為參數傳遞:
import com.cars.buildCar
val testCar = Car ()
mockkStatic(::buildCar)
every { buildCar() } returns testCar
assertEquals(testCar, buildCar())
verify { buildCar() }模擬函數將清除同一文件中聲明的其他功能的任何現有模擬,相當於在生成的封閉類上調用clearStaticMockk 。
Kotlin中有三種類型的擴展功能:
對於對像或類,您可以通過創建常規mockk來模擬擴展功能:
data class Obj ( val value : Int )
class Ext {
fun Obj. extensionFunc () = value + 5
}
with (mockk< Ext >()) {
every {
Obj ( 5 ).extensionFunc()
} returns 11
assertEquals( 11 , Obj ( 5 ).extensionFunc())
verify {
Obj ( 5 ).extensionFunc()
}
}要模擬模塊範圍的擴展功能,您需要將模塊名稱作為參數構建mockkStatic(...) 。例如, pkg軟件包中的模塊File.kt的“ pkg.filekt”。
data class Obj ( val value : Int )
// declared in File.kt ("pkg" package)
fun Obj. extensionFunc () = value + 5
mockkStatic( " pkg.FileKt " )
every {
Obj ( 5 ).extensionFunc()
} returns 11
assertEquals( 11 , Obj ( 5 ).extensionFunc())
verify {
Obj ( 5 ).extensionFunc()
}在jvm環境中,您可以用功能參考替換類名:
mockkStatic( Obj ::extensionFunc)請注意,這將模擬整個pkg.FileKt類,而不僅僅是extensionFunc 。
該語法還適用於擴展屬性:
val Obj .squareValue get() = value * value
mockkStatic( Obj ::squareValue)如果使用@JvmName ,請將其指定為類名稱。
khttp.kt:
@file:JvmName( " KHttp " )
package khttp
// ... KHttp code 測試代碼:
mockkStatic( " khttp.KHttp " )有時,您需要更多一點了解擴展功能。例如,擴展功能File.endsWith()具有完全不可預測的classname :
mockkStatic( " kotlin.io.FilesKt__UtilsKt " )
every { File ( " abc " ).endsWith(any< String >()) } returns true
println ( File ( " abc " ).endsWith( " abc " ))這是標準的Kotlin行為,可能是無法預測的。使用Tools -> Kotlin -> Show Kotlin Bytecode或檢查.class文件以檢測此類名稱。
從1.9.1版本中,可以進行更多擴展的vararg處理:
interface ClsWithManyMany {
fun manyMany ( vararg x : Any ): Int
}
val obj = mockk< ClsWithManyMany >()
every { obj.manyMany( 5 , 6 , * varargAll { it == 7 }) } returns 3
println (obj.manyMany( 5 , 6 , 7 )) // 3
println (obj.manyMany( 5 , 6 , 7 , 7 )) // 3
println (obj.manyMany( 5 , 6 , 7 , 7 , 7 )) // 3
every { obj.manyMany( 5 , 6 , * anyVararg(), 7 ) } returns 4
println (obj.manyMany( 5 , 6 , 1 , 7 )) // 4
println (obj.manyMany( 5 , 6 , 2 , 3 , 7 )) // 4
println (obj.manyMany( 5 , 6 , 4 , 5 , 6 , 7 )) // 4
every { obj.manyMany( 5 , 6 , * varargAny { nArgs > 5 }, 7 ) } returns 5
println (obj.manyMany( 5 , 6 , 4 , 5 , 6 , 7 )) // 5
println (obj.manyMany( 5 , 6 , 4 , 5 , 6 , 7 , 7 )) // 5
every {
obj.manyMany( 5 , 6 , * varargAny {
if (position < 3 ) it == 3 else it == 4
}, 7 )
} returns 6
println (obj.manyMany( 5 , 6 , 3 , 4 , 7 )) // 6
println (obj.manyMany( 5 , 6 , 3 , 4 , 4 , 7 )) // 6如果您需要模擬私人功能,則可以通過動態調用來執行此操作。
class Car {
fun drive () = accelerate()
private fun accelerate () = " going faster "
}
val mock = spyk< Car >(recordPrivateCalls = true )
every { mock[ " accelerate " ]() } returns " going not so fast "
assertEquals( " going not so fast " , mock.drive())
verifySequence {
mock.drive()
mock[ " accelerate " ]()
}如果要驗證私人電話,則應使用recordPrivateCalls = true創建一個spyk
此外,更多的詳細語法允許您獲得和設置屬性,並結合相同的動態調用:
val mock = spyk( Team (), recordPrivateCalls = true )
every { mock getProperty " speed " } returns 33
every { mock setProperty " acceleration " value less( 5 ) } just runs
justRun { mock invokeNoArgs " privateMethod " }
every { mock invoke " openDoor " withArguments listOf ( " left " , " rear " ) } returns " OK "
verify { mock getProperty " speed " }
verify { mock setProperty " acceleration " value less( 5 ) }
verify { mock invoke " openDoor " withArguments listOf ( " left " , " rear " ) }您可以通過fieldValue訪問襯板字段,並使用value來設置值。
注意:在下面的示例中,我們使用propertyType指定fieldValue的類型。這是需要的,因為可以自動捕獲Getter的類型。使用nullablePropertyType指定一個無效類型。
val mock = spyk( MockCls (), recordPrivateCalls = true )
every { mock.property } answers { fieldValue + 6 }
every { mock.property = any() } propertyType Int :: class answers { fieldValue + = value }
every { mock getProperty " property " } propertyType Int :: class answers { fieldValue + 6 }
every { mock setProperty " property " value any< Int >() } propertyType Int :: class answers { fieldValue + = value }
every {
mock.property = any()
} propertyType Int :: class answers {
fieldValue = value + 1
} andThen {
fieldValue = value - 1
}通過接口添加其他行為並使它們固執:
val spy = spyk( System . out , moreInterfaces = arrayOf( Runnable :: class ))
spy. println ( 555 )
every {
(spy as Runnable ). run ()
} answers {
(self as PrintStream ). println ( " Run! Run! Run! " )
}
val thread = Thread (spy as Runnable )
thread.start()
thread.join()這裡沒什麼特別的。如果您有函數Nothing返回:
fun quit ( status : Int ): Nothing {
exitProcess(status)
}例如,您可以將異常作為行為:
every { quit( 1 ) } throws Exception ( " this is a test " )一個示波器模擬是一個模擬,該模擬在執行參數後傳遞後自動解鎖自身。您可以使用mockkObject , mockkStatic和mockkConstructor函數。
object ObjBeingMocked {
fun add ( a : Int , b : Int ) = a + b
}
// ObjBeingMocked will be unmocked after this scope
mockkObject( ObjBeingMocked ) {
assertEquals( 3 , ObjBeingMocked .add( 1 , 2 ))
every { ObjBeingMocked .add( 1 , 2 ) } returns 55
assertEquals( 55 , ObjBeingMocked .add( 1 , 2 ))
}創建新匹配器的一種非常簡單的方法是將功能連接到MockKMatcherScope或MockKVerificationScope並使用match函數:
fun MockKMatcherScope. seqEq ( seq : Sequence < String >) = match< Sequence < String >> {
it.toList() == seq.toList()
}也可以通過實現Matcher接口來創建更高級的匹配器。
自定義匹配器的示例,該匹配器在沒有順序的情況下比較列表:
@Test
fun test () {
class MockCls {
fun op ( a : List < Int >) = a.reversed()
}
val mock = mockk< MockCls >()
every { mock.op(any()) } returns listOf ( 5 , 6 , 9 )
println (mock.op( listOf ( 1 , 2 , 3 )))
verify { mock.op(matchListWithoutOrder( 3 , 2 , 1 )) }
}
data class ListWithoutOrderMatcher < T >(
val expectedList : List < T >,
val refEq : Boolean
) : Matcher<List<T>> {
val map = buildCountsMap(expectedList, refEq)
override fun match ( arg : List < T > ? ): Boolean {
if (arg == null ) return false
return buildCountsMap(arg, refEq) == map
}
private fun buildCountsMap ( list : List < T >, ref : Boolean ): Map < Any ?, Int > {
val map = mutableMapOf< Any ?, Int >()
for (item in list) {
val key = when {
item == null -> nullKey
refEq -> InternalPlatform .ref(item)
else -> item
}
map.compute(key, { _, value -> (value ? : 0 ) + 1 })
}
return map
}
override fun toString () = " matchListWithoutOrder( $expectedList ) "
@Suppress( " UNCHECKED_CAST " )
override fun substitute ( map : Map < Any , Any >): Matcher < List < T >> {
return copy(expectedList = expectedList.map { map.getOrDefault(it as Any? , it) } as List < T >)
}
companion object {
val nullKey = Any ()
}
}
inline fun < reified T : List < E >, E : Any > MockKMatcherScope. matchListWithoutOrder (
vararg items : E ,
refEq : Boolean = true
): T = match( ListWithoutOrderMatcher ( listOf ( * items), refEq))示例使用反射模擬構建器式對像上的所有方法
val builderFunctions = MyBuilder :: class .memberFunctions.filter { it.returnType.classifier == MyBuilder :: class }
val builderMock = mockk< MyBuilder > {
builderFunctions.forEach { func ->
every {
val params = listOf< Any ?>(builderMock) + func.parameters.drop( 1 ).map { any(it.type.classifier as KClass < Any >) }
func.call( * params.toTypedArray())
} answers {
this @mockk
}
}
}要在全球調整參數,您可以在資源文件中指定一些設置。
如何使用:
src/main/resources中創建一個io/mockk/settings.properties文件。 relaxed =true|false
relaxUnitFun =true|false
recordPrivateCalls =true|false
stackTracesOnVerify =true|false
stackTracesAlignment =left|center stackTracesAlignment確定是將堆棧跟踪與中心對齊(默認值),還是向左(與通常的JVM stacktraces更一致)。
這裡有幾張桌子可以幫助您掌握DSL。
| 功能 | 描述 |
|---|---|
mockk<T>(...) | 建立一個常規模擬 |
spyk<T>() | 使用默認構造函數構建間諜 |
spyk(obj) | 通過從obj複製來建立間諜 |
slot | 創建一個捕獲的插槽 |
every | 開始一個固定塊 |
coEvery | 為Coroutines開始一個固定塊 |
verify | 開始驗證塊 |
coVerify | 開始驗證塊 |
verifyAll | 啟動一個驗證塊,應包括所有調用 |
coVerifyAll | 啟動一個驗證塊,該塊應包括所有調用的調用 |
verifyOrder | 開始檢查訂單的驗證塊 |
coVerifyOrder | 啟動一個驗證塊,檢查Coroutines的訂單 |
verifySequence | 啟動一個驗證塊,該塊檢查是否以指定的順序進行所有調用 |
coVerifySequence | 啟動一個驗證塊,該塊檢查是否以指定順序為Coroutines進行了所有調用 |
excludeRecords | 從記錄中排除一些電話 |
confirmVerified | 確認所有記錄的電話均已驗證 |
checkUnnecessaryStub | 確認所有錄製的電話至少一次使用一次 |
clearMocks | 清除指定的模擬 |
registerInstanceFactory | 允許您重新定義某些對象實例化的方式 |
mockkClass | 通過將課程作為參數傳遞來構建常規模擬 |
mockkObject | 將對像變成對像模擬,或將其清除,如果已經轉換為 |
unmockkObject | 將對像模擬回到常規對象 |
mockkStatic | 從班級中進行靜態模擬,或者如果已經轉換了 |
unmockkStatic | 將靜態模擬重新變成常規課程 |
clearStaticMockk | 清除一個靜態模擬 |
mockkConstructor | 使構造函數從課堂上模擬,或者如果已經進行了轉換,則將其清除 |
unmockkConstructor | 將構造函數模擬轉回常規課程 |
clearConstructorMockk | 清除構造函數模擬 |
unmockkAll | 解除對象,靜態和構造函數模擬 |
clearAllMocks | 清除常規,對象,靜態和構造函數模擬 |
默認情況下,使用eq()匹配簡單的參數
| 匹配器 | 描述 |
|---|---|
any() | 匹配任何參數 |
any(Class) | 匹配給出類的任何參數(用於反思嘲笑) |
allAny() | 使用any()而不是eq()的特殊匹配器作為簡單參數提供的匹配器 |
isNull() | 檢查值是否為null |
isNull(inverse=true) | 檢查值是否不是null |
ofType(type) | 檢查該值是否屬於該類型 |
match { it.startsWith("string") } | 通過傳遞的謂詞進行匹配 |
coMatch { it.startsWith("string") } | 通過傳遞的Coroutine謂詞進行匹配 |
matchNullable { it?.startsWith("string") } | 通過傳遞的謂詞匹配無效的值 |
coMatchNullable { it?.startsWith("string") } | 通過傳遞的coroutine謂詞匹配無效的值 |
eq(value) | 匹配如果值等於提供的值通過deepEquals函數 |
eq(value, inverse=true) | 匹配如果該值不等於通過deepEquals函數提供的值 |
neq(value) | 匹配如果該值不等於通過deepEquals函數提供的值 |
refEq(value) | 匹配是否通過參考比較等於提供的值 |
refEq(value, inverse=true) | 匹配是否通過參考比較等於提供的值 |
nrefEq(value) | 匹配是否通過參考比較等於提供的值 |
cmpEq(value) | 匹配值是否等於通過compareTo函數提供的值 |
less(value) | 匹配值是否小於通過compareTo函數提供的值 |
more(value) | 匹配值是否大於通過compareTo函數提供的值 |
less(value, andEquals=true) | 匹配值是否小於或等於通過compareTo函數提供的值 |
more(value, andEquals=true) | 匹配該值是否超過或等於通過compareTo函數提供的值 |
range(from, to, fromInclusive=true, toInclusive=true) | 匹配如果該值通過compareTo函數在範圍內 |
and(left, right) | 通過邏輯和 |
or(left, right) | 通過邏輯或 |
not(matcher) | 否定匹配者 |
capture(slot) | 捕獲CapturingSlot的無效價值 |
captureNullable(slot) | 捕獲CapturingSlot的無效價值 |
capture(mutableList) | 捕獲列表的價值 |
captureNullable(mutableList) | 捕獲具有零值的列表的值 |
captureLambda() | 捕獲一個lambda |
captureCoroutine() | 捕獲一個Coroutine |
invoke(...) | 調用匹配的參數 |
coInvoke(...) | 調用一個匹配的coroutine參數 |
hint(cls) | 暗示下一種返回類型,以防它被刪除 |
anyVararg() | 匹配vararg中的任何元素 |
varargAny(matcher) | 匹配如果有任何元素匹配匹配器 |
varargAll(matcher) | 匹配如果所有元素匹配匹配器 |
any...Vararg() | 匹配Vararg中的任何元素(特定於原始類型) |
varargAny...(matcher) | 匹配如果任何元素匹配匹配器(特定於原始類型) |
varargAll...(matcher) | 匹配如果所有元素都匹配匹配器(特定於原始類型) |
僅在驗證模式下可用的一些特殊匹配器:
| 匹配器 | 描述 |
|---|---|
withArg { code } | 匹配任何值並允許執行一些代碼 |
withNullableArg { code } | 匹配任何無效的值並允許執行某些代碼 |
coWithArg { code } | 匹配任何值並允許執行一些Coroutine代碼 |
coWithNullableArg { code } | 匹配任何無效的值,並允許執行某些Coroutine代碼 |
| 驗證器 | 描述 |
|---|---|
verify { mock.call() } | 無需驗證是否進行了呼叫 |
verify(inverse=true) { mock.call() } | 是否無序驗證未進行呼叫 |
verify(atLeast=n) { mock.call() } | 是否無序驗證至少n次進行呼叫 |
verify(atMost=n) { mock.call() } | 做出無序的驗證是否在最多n次進行呼叫 |
verify(exactly=n) { mock.call() } | 做出無序的驗證,是否n進行呼叫 |
verifyAll { mock.call1(); mock.call2() } | 是否無序的驗證僅執行了上述模擬的指定呼叫 |
verifyOrder { mock.call1(); mock.call2() } | 驗證通話順序接一個 |
verifySequence { mock.call1(); mock.call2() } | 請驗證是否僅執行上述模擬執行指定的呼叫順序 |
verify { mock wasNot Called } | 驗證未撥打模擬 |
verify { listOf(mock1, mock2) wasNot Called } | 驗證是否未調用模擬列表 |
答案可以跟進一個或多個其他答案。
| 回答 | 描述 |
|---|---|
returns value | 指定匹配的呼叫返回指定值 |
returnsMany list | 指定匹配的調用返回列表中的值,然後隨後的調用返回下一個元素 |
returnsArgument(n) | 指定匹配的呼叫返回該調用的第n個參數 |
throws ex | 指定匹配的呼叫拋出異常 |
throwsMany list | 指定匹配的呼叫從列表中拋出異常,隨後的呼叫拋出了下一個異常 |
answers { code } | 指定具有帶有answer scope的代碼塊的匹配呼叫答案 |
coAnswers { code } | 指定具有帶有answer scope的Coroutine代碼塊的匹配呼叫答案 |
answers answerObj | 指定使用答案對象匹配的呼叫答案 |
answers { nothing } | 指定匹配的呼叫答案null |
just Runs | 指定匹配的呼叫是返回單元(返回null) |
just Awaits | 指定匹配的呼叫永不返回(自v1.13.3以來可用) |
propertyType Class | 指定背景字段登錄器的類型 |
nullablePropertyType Class | 將襯板登錄器的類型指定為無效類型 |
每個隨後的呼叫都會返回下一個答案,最後一個值持續存在。因此,這類似於returnsMany語義。
| 其他答案 | 描述 |
|---|---|
andThen value | 指定匹配的呼叫返回一個指定的值 |
andThenMany list | 指定匹配的調用返回列表中的值,然後隨後的調用返回下一個元素 |
andThenThrows ex | 指定匹配的呼叫拋出異常 |
andThenThrowsMany ex | 指定匹配的呼叫從列表中拋出異常,隨後的呼叫拋出了下一個異常 |
andThen { code } | 指定具有帶有answer scope的代碼塊的匹配呼叫答案 |
coAndThen { code } | 指定具有帶有answer scope的Coroutine代碼塊的匹配呼叫答案 |
andThenAnswer answerObj | 指定使用答案對象匹配的呼叫答案 |
andThen { nothing } | 指定匹配的呼叫答案null |
andThenJust Runs | 指定匹配的呼叫是返回單元(自V1.12.2以來可用) |
andThenJust Awaits | 指定匹配的呼叫永遠不會返回(自V1.13.3以來可用) |
| 範圍 | 描述 |
|---|---|
call | 由調用和匹配器組成的調用對象 |
invocation | 包含有關調用實際功能的信息 |
matcher | 包含有關用於匹配調用的匹配器的信息 |
self | 引用對象調用 |
method | 參考功能調用 |
args | 引用調用參數 |
nArgs | 調用參數的數量 |
arg(n) | 第n個論點 |
firstArg() | 第一個參數 |
secondArg() | 第二個論點 |
thirdArg() | 第三個論點 |
lastArg() | 最後一個論點 |
captured() | 列表中的最後一個元素在捕獲到列表時方便 |
lambda<...>().invoke() | 致電被捕獲的lambda |
coroutine<...>().coInvoke() | 致電被捕獲的Coroutine |
nothing | nothing返回作為答案的零值 |
fieldValue | 屬性支持字段的登錄器 |
fieldValueAny | 帶有屬性備份字段的登錄器Any?類型 |
value | 設置的值,將與屬性備份字段相同的類型鑄造 |
valueAny | 設置價值,有Any?類型 |
callOriginal | 調用原始功能 |
| 範圍 | 描述 |
|---|---|
position | 參數在vararg數組中的位置 |
nArgs | vararg數組中參數的總體計數 |
您還可以通過成為贊助商來支持該項目。您的徽標將在此處顯示您網站的鏈接。
感謝我們所有的支持者!
由於所有貢獻的人,該項目的存在。
要提出問題,請使用堆棧溢出或吉特。
要報告錯誤,請使用GitHub項目。