您需要开始的只是为了在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项目。