始めるために必要なのは、 MockKライブラリへの依存関係を追加することだけです。
| アプローチ | 命令 |
|---|---|
![]() | 証言 "io.mockk:mockk:$ {mockkversion}"
|
(Kotlin DSL) | 証言( "io.mockk:mockk:$ {mockkversion}") |
![]() | <依存関係>
<GroupId> io.mockk </groupId>
<artifactid> mockk-jvm </artifactid>
<バージョン> $ {mockkversion} </version>
<scope>テスト</scope>
</依存関係>
|
証言 "io.mockk:mockk-android:$ {mockkversion}"
証言 "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の一連の記事「Mocking Is Not Rocket Science」をチェックしてください。アカデミーは、すべての高度な機能の説明に至るまでock笑するという基本からモッククを説明しています。
基本
予想される行動と行動の検証
mockk機能
MOCKK高度な機能
Kotlin、Junit、MockkでのQuarkusのテスト
Mockkの黒い魔法の解明(EN、翻訳)
Mockkガイドブック
Marco Cattaneoによる「MockkでのKotlinユニットテスト」
(ビデオ)mockkで検証を使用して、mockedオブジェクトの関数呼び出しを検証する
raywenderlich.comでMOCKK有料コースでテストします
Androidビデオチュートリアルパート1、パート2ライアンケイのTDD
(ビデオ)Android Developer Live Coding#13:Mockk、Coroutines、Test駆動型開発によるユニットテスト
Kotlinconf 2018-フィリップ・ハウアーによるコトリンでのユニットテストのベストプラクティス
Kotlin-Fullstack-Sampleは、テストでカバーされている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を使用します。短い表記の場合は、 @InjectMockKsと同じことをデフォルトで行うが、これらの2つのフラグをオンにする@OverrideMockKs使用してください。
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を呼び出し、各テストクラスの実行後にテスト環境がきれいになるようにします。 mockk.junit.extension.keepmocks=trueプロパティを設定して、 @MockKExtension.KeepMocksアノテーションをクラスまたはグローバルに追加することにより、この動作を無効にできます。 (v1.13.11以降)代わりに、デフォルトでclearAllMocks ( currentThreadOnly=false )はスレッドセーフではないため、並行してテストを実行する必要がある場合は、 mockk.junit.extension.requireParallelTesting=true MockKExtension.RequireParallelTestingテンションを追加することができ@AfterAll 。 clearAllMocksが明示的に呼び出された場合、 clearAllMocks(currentThreadOnly = true)を提供できるため、同じスレッド内で作成されたモックのみをクリアできます(v1.13.12以降)。
テストクラスに@MockKExtension.ConfirmVerificationを注釈することにより、すべてのスタブの方法が実際に検証されていることを確認できます。
これにより、各テスト後にすべてのモックでconfirmVerified内部的に呼び出して、不必要な頑丈なものがないことを確認します。
IDEでテストを実行するときにこの動作は期待どおりに機能しない場合があります。これは、これらのconfirmVerified呼び出しが失敗したときにスローされる例外を処理するのに役立つGradleであるためです。
テストクラスに@MockKExtension.CheckUnnecessaryStubで注釈を付けることにより、すべてのスタブの方法が有用であることを確認できます - 少なくとも1回は使用できます。
これにより、各テスト後にすべてのモックで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 calledUnit - 戻り機能をリラックスさせる場合は、 relaxUnitFun = true mockk関数、 @MockK annotation、またはMockKAnnotations.init関数の引数として使用できます。
関数:
mockk< ClassBeingMocked >(relaxUnitFun = true )注釈:
@MockK(relaxUnitFun = true )
lateinit var mock1 : ClassBeingMocked
init {
MockKAnnotations . init ( this )
}mockkannotations.init:
@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 ) }enumsはmockkObjectを使用してock笑できます。
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)特に所有していないコードでは、新しく作成されたオブジェクトをmockする必要があります。この目的のために、次のコンストラクトが提供されます。
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には、クラスごとに1つのインスタンスがあります。コール録音は、 prototype mockにも行われます。
関数の動作が指定されていない場合、元の関数が実行されます。
クラスに複数のコンストラクターがある場合、それぞれを個別にock笑することができます。
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 )
}この場合、 prototype mockが作成されたすべてのセットのマッチャーごとにconstructedWithれていることに注意してください。
通常の引数とマッチャーの両方を混ぜることができます。
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は通常、Factory Method slot<T : Any?>()を介して作成され、nullable型および非ヌル可能なタイプをキャプチャすることが可能です。 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 ) }
}これは、次の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 }コルーチンを模倣するには、サポートライブラリに別の依存関係を追加する必要があります。
| 等級 |
|---|
証言 "org.jetbrains.kotlinx:kotlinx-coroutines-core:xx" |
| メイベン |
|---|
<依存関係>
<groupId> org.jetbrains.kotlinx </groupId>
<artifactid> kotlinx-coroutines-core </artifactid>
<バージョン> xx </version>
<scope>テスト</scope>
</依存関係> |
次に、 coEvery 、 coVerify 、 coMatch 、 coAssert 、Coassert、 coRun 、 coAnswers 、 coInvokeを使用して、Mock Suspend関数を使用できます。
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を使用してock笑できます。関数をインポートし、引数として参照を渡すだけです。
import com.cars.buildCar
val testCar = Car ()
mockkStatic(::buildCar)
every { buildCar() } returns testCar
assertEquals(testCar, buildCar())
verify { buildCar() }関数をock笑すると、同じファイルで宣言された他の関数の既存のモックがクリアclearStaticMockkれます。
Kotlinには拡張機能には3種類あります。
オブジェクトまたはクラスの場合、通常のmockkを作成するだけで、拡張機能をmockすることができます。
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()
}
}モジュール全体の拡張機能をmockするには、モジュールのクラス名を引数として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)これにより、 extensionFuncだけでなく、 pkg.FileKtクラス全体をock笑することに注意してください。
この構文は、拡張プロパティにも適用されます。
val Obj .squareValue get() = value * value
mockkStatic( Obj ::squareValue) @JvmNameが使用されている場合は、クラス名として指定してください。
khttp.kt:
@file:JvmName( " KHttp " )
package khttp
// ... KHttp code テストコード:
mockkStatic( " khttp.KHttp " )拡張機能をock笑するには、もう少し知る必要がある場合があります。たとえば、拡張機能File.endsWith()は、完全に予測不可能なclassnameがあります。
mockkStatic( " kotlin.io.FilesKt__UtilsKt " )
every { File ( " abc " ).endsWith(any< String >()) } returns true
println ( File ( " abc " ).endsWith( " abc " ))これは、予測不可能な標準的なコトリン動作です。 Tools -> Kotlin -> Show Kotlin Bytecodeか、JARアーカイブの.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のタイプを指定します。これは、ゲッター用にタイプを自動的にキャプチャできるため、必要です。 nullablePropertyTypeを使用して、Nullableタイプを指定します。
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
}
}
}パラメーターをグローバルに調整するには、リソースファイルで指定できる設定がいくつかあります。
使い方:
io/mockk/settings.propertiesファイルをsrc/main/resourcesで作成します。 relaxed =true|false
relaxUnitFun =true|false
recordPrivateCalls =true|false
stackTracesOnVerify =true|false
stackTracesAlignment =left|center stackTracesAlignment 、スタックトレースを中心(デフォルト)に揃えるか左に並べるか(通常のJVMスタックトレースとより一致するか)を決定します。
DSLを習得するのに役立ついくつかのテーブルを以下に示します。
| 関数 | 説明 |
|---|---|
mockk<T>(...) | 通常のモックを構築します |
spyk<T>() | デフォルトのコンストラクターを使用してスパイを構築します |
spyk(obj) | objからコピーしてスパイを構築します |
slot | キャプチャスロットを作成します |
every | スタブブロックを起動します |
coEvery | コルーチンのスタブブロックを起動します |
verify | 検証ブロックを開始します |
coVerify | コルーチンの検証ブロックを開始します |
verifyAll | すべての呼び出しを含める必要がある検証ブロックを開始します |
coVerifyAll | Coroutinesのすべての呼び出しを含む必要がある検証ブロックを開始します |
verifyOrder | 注文をチェックする検証ブロックを開始します |
coVerifyOrder | Coroutinesの注文をチェックする検証ブロックを開始します |
verifySequence | すべての呼び出しが指定されたシーケンスで行われたかどうかをチェックする検証ブロックを開始します |
coVerifySequence | すべての呼び出しがコルーチンの指定されたシーケンスで行われたかどうかをチェックする検証ブロックを開始します |
excludeRecords | 録音されてからいくつかの呼び出しを除外します |
confirmVerified | 記録されたすべての呼び出しが検証されたことを確認します |
checkUnnecessaryStub | 記録されたすべての呼び出しが少なくとも一度は使用されることを確認します |
clearMocks | 指定されたモックをクリアします |
registerInstanceFactory | 特定のオブジェクトのインスタンス化の方法を再定義することができます |
mockkClass | クラスをパラメーターとして渡すことにより、通常の模擬を構築します |
mockkObject | オブジェクトをオブジェクトのモックに変えるか、すでに変換されている場合はクリアします |
unmockkObject | オブジェクトをモックして通常のオブジェクトに戻します |
mockkStatic | クラスから静的なモックを作成するか、すでに変換されている場合にクリアします |
unmockkStatic | 静的モックを通常のクラスに戻します |
clearStaticMockk | 静的なモックをクリアします |
mockkConstructor | コンストラクターをクラスからモックアウトするか、すでに変換されている場合にクリアします |
unmockkConstructor | コンストラクターのモックを通常のクラスに戻します |
clearConstructorMockk | コンストラクターのモックをクリアします |
unmockkAll | オブジェクト、静的、コンストラクターのモックを解除します |
clearAllMocks | 通常のオブジェクト、静的、コンストラクターのモックをクリアします |
デフォルトでは、 eq()を使用して単純な引数が一致します
| マッチャー | 説明 |
|---|---|
any() | 引数に一致します |
any(Class) | Giveクラスの議論に一致します(反射的なモッキング用) |
allAny() | 単純な引数として提供されるマッチャーにeq()の代わりにany()を使用する特別なマッチャー |
isNull() | 値がnullかどうかを確認します |
isNull(inverse=true) | 値がnullでないかどうかを確認します |
ofType(type) | 値がタイプに属しているかどうかを確認します |
match { it.startsWith("string") } | 合格された述語を介して一致します |
coMatch { it.startsWith("string") } | 合格したコルーチン述語を介して一致します |
matchNullable { it?.startsWith("string") } | 合格された述語を介して無効な値を一致させます |
coMatchNullable { it?.startsWith("string") } | 合格したコルーチン述語を介してヌル可能な値を一致させます |
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に非怠nul的な値をキャプチャします |
captureNullable(slot) | CapturingSlotに無効な値をキャプチャします |
capture(mutableList) | リストの値をキャプチャします |
captureNullable(mutableList) | null値と一緒にリストの値をキャプチャします |
captureLambda() | ラムダをキャプチャします |
captureCoroutine() | コルーチンをキャプチャします |
invoke(...) | 一致した引数を呼びます |
coInvoke(...) | コルーチンの一致した引数を呼び出します |
hint(cls) | 消去された場合に備えて、次の返品タイプをヒントします |
anyVararg() | Varargの要素を一致させます |
varargAny(matcher) | 一致する要素がマッチャーに一致する場合 |
varargAll(matcher) | すべての要素がマッチャーに一致する場合に一致します |
any...Vararg() | Varargの要素に一致します(原始タイプに固有) |
varargAny...(matcher) | 一致する要素がマッチャーに一致する場合(プリミティブタイプに固有) |
varargAll...(matcher) | すべての要素がマッチャーに一致する場合(プリミティブタイプに固有) |
検証モードでのみ利用可能ないくつかの特別なマッチャー:
| マッチャー | 説明 |
|---|---|
withArg { code } | 任意の値に一致し、一部のコードを実行できます |
withNullableArg { code } | 無駄な値に一致し、いくつかのコードを実行できます |
coWithArg { code } | 任意の値に一致し、いくつかのコルーチンコードを実行できます |
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 } | 模擬のリストが呼ばれなかったことを確認してください |
回答の後に、1つ以上の追加の回答が続くことができます。
| 答え | 説明 |
|---|---|
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 | 一致した呼び出しが1つの指定された値を返すことを指定します |
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() | 2番目の議論 |
thirdArg() | 3番目の議論 |
lastArg() | 最後の議論 |
captured() | リストのキャプチャをキャプチャする際の利便性のためのリストの最後の要素 |
lambda<...>().invoke() | 捕獲されたラムダに電話してください |
coroutine<...>().coInvoke() | キャプチャされたコルーチンを呼び出します |
nothing | 答えとしてnothing返すためのヌル価値 |
fieldValue | プロパティバッキングフィールドへのアクセサ |
fieldValueAny | プロパティバッキングフィールドへのアクセサはAny?タイプ |
value | 設定されている値、プロパティバッキングフィールドと同じタイプにキャスト |
valueAny | 価値がありますAny?タイプ |
callOriginal | 元の関数を呼び出します |
| パラメーター | 説明 |
|---|---|
position | Varargアレイの引数の位置 |
nArgs | Varargアレイの引数の全体的なカウント |
また、スポンサーになることでこのプロジェクトをサポートすることもできます。あなたのロゴは、あなたのウェブサイトへのリンクでここに表示されます。
すべての支援者に感謝します!
このプロジェクトは、貢献するすべての人々のおかげで存在します。
質問するには、Stack OverflowまたはGitterを使用してください。
バグを報告するには、GitHubプロジェクトを使用してください。