Все, что вам нужно для начала, это просто добавить зависимость в библиотеку MockK .
| Подход | Инструкция |
|---|---|
![]() | teptimplementation "io.mockk: Mockk: $ {mockkversion}"
|
(Kotlin DSL) | teptImplementation ("io.mockk: mockk: $ {mockkversion}") |
![]() | <Зависимость>
<groupId> io.mockk </GroupId>
<Artifactid> mockk-jvm </artifactid>
<версия> $ {mockkversion} </version>
<Scope> тест </scope>
</Зависимость>
|
teptimplementation "io.mockk: mockk-android: $ {mockkversion}"
teptimplementation "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 Advanced Features
Тестирование Quarkus с котлином, Junit и Mockk
Разрушать черную магию Мокека (en, перевод)
Mockk Guidebook
«Котлинский модульный тестирование с Mockk» Марко Каттанео
(Видео) Используйте Verify в Mockk, чтобы проверить вызовы функций на высмеиваемом объекте
Тестирование с платным курсом Mockk на raywenderlich.com
TDD для Android Video Tutorial, часть 2, часть 2 от Райана Кея
(Видео) Android Developer Live Coding #13: модульное тестирование с Mockk, Coroutines, Devilited Development
Kotlinconf 2018 - Лучшие методы для подразделения в Котлин от Филиппа Хауэра
Kotlin-fullstack-Sample использует проект Mockk, покрытый тестами
Dzone Article
Статья Habrahabr (RU)
Насмешка в котлине с Mockk - 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
} Наконец, это расширение позвонит unmockkAll и clearAllMocks в обратном вызове @AfterAll , обеспечивая чистоту вашей тестовой среды после каждого выполнения тестового класса. Вы можете отключить это поведение, добавив аннотацию @MockKExtension.KeepMocks в свой класс или во всем мире, установив mockk.junit.extension.keepmocks=true Property. (Поскольку v1.13.11) Альтернативно, поскольку clearAllMocks по умолчанию ( currentThreadOnly=false ) не является потоком, если вам нужно запустить тест параллельно, вы можете добавить MockKExtension.RequireParallelTesting Annotation в свой класс или установить mockk.junit.extension.requireParallelTesting=true infater in thisaftrack in the infater in thisaftrack in thisaftrack in the in the in the in the in @AfterAll Если clearAllMocks явно называется, вы можете подать clearAllMocks(currentThreadOnly = true) чтобы он только очищал макет, созданные в одном и том же потоке (с V1.13.12).
Вы можете убедиться, что все загрязненные методы фактически подтверждаются, также аннотируя ваш тестовый класс с помощью @MockKExtension.ConfirmVerification .
Это будет внутренне вызовов confirmVerified всеми макетами после каждого теста, чтобы убедиться, что нет ненужных загрязнений.
Обратите внимание, что это поведение может не сработать, как и ожидалось, когда вы работаете в вашей IDE, так как именно Градл позаботится об обращении с исключением, которое выдвигается, когда эти 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: Объект SPY - это копия пропущенного объекта. ПРИМЕЧАНИЕ 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 или функции 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()
}Несмотря на ограничения языка котлин, вы можете создать новые экземпляры объектов, если это необходимо, тестируя логику:
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 .
Измешное поведение такого макета связано со специальным prototype mock обозначенным anyConstructed<MockCls>() .
Есть один экземпляр на класс такого 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 )
} Обратите внимание, что в этом случае 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 обычно создается с помощью заводского 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 }Чтобы издеваться над коратиками, вам нужно добавить еще одну зависимость в библиотеку поддержки.
| Градл |
|---|
teptimplementation "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 , 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
Котлин позволяет объявлять функции, которые не принадлежат ни одному классу или объекту, называемым функциями верхнего уровня. Эти вызовы переводятся в статические методы в средах 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.filekt" для module File.kt в пакете pkg .
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 " )) Это стандартное поведение котлина, которое может быть непредсказуемым. Используйте 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 " ]()
} Если вы хотите проверить частные вызовы, вы должны создать spyk с recordPrivateCalls = true
Кроме того, более многословный синтаксис позволяет получить свойства и устанавливать свойства в сочетании с одинаковыми динамическими вызовами:
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
}
}
}Чтобы настроить параметры по всему миру, есть несколько настройки, которые вы можете указать в файле ресурса.
Как использовать:
io/mockk/settings.properties в src/main/resources . 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 | Запускает блок проверки для Coroutines |
verifyAll | Запускает блок проверки, который должен включать все вызовы |
coVerifyAll | Запускает блок проверки, который должен включать в себя все вызовы для Coroutines |
verifyOrder | Запускает блок проверки, который проверяет заказ |
coVerifyOrder | Запускает блок проверки, который проверяет заказ на Coroutines |
verifySequence | Запускает блок проверки, который проверяет, были ли сделаны все вызовы в указанной последовательности |
coVerifySequence | Запускает блок проверки, который проверяет, были ли сделаны все вызовы в указанной последовательности для CORUTINES |
excludeRecords | Исключить несколько звонков из записи |
confirmVerified | подтверждает, что все записанные вызовы были проверены |
checkUnnecessaryStub | подтверждает, что все записанные вызовы используются хотя бы один раз |
clearMocks | Очистки указанных издеваний |
registerInstanceFactory | Позволяет переопределить путь экземпляра для определенного объекта |
mockkClass | Создает обычную макет, передавая класс как параметр |
mockkObject | превращает объект в макет объекта или очищает его, если уже был преобразован |
unmockkObject | превращает объект издеваться в обычный объект |
mockkStatic | делает статическое макет из класса или очищает его, если он уже был преобразован |
unmockkStatic | превращает статическое макет обратно в обычный класс |
clearStaticMockk | очищает статическое макет |
mockkConstructor | делает конструктор издеваться из класса или очищает его, если он уже был преобразован |
unmockkConstructor | Поворачивает конструктор в обычный класс |
clearConstructorMockk | очищает конструктор |
unmockkAll | Uncocks Object, Static и Constructor Mocks |
clearAllMocks | Ошибкие, регулярные, объектные, статические и конструкторы |
По умолчанию простые аргументы соответствуют с использованием eq()
| Маттер | Описание |
|---|---|
any() | соответствует любым аргументам |
any(Class) | соответствует любым аргументам класса дай (для рефлексивного насмешки) |
allAny() | Специальный сопоставщик, который использует any() вместо eq() для совпадений, которые предоставляются в качестве простых аргументов |
isNull() | проверяет, является ли значение нулевым |
isNull(inverse=true) | проверяет, не является ли значение нулевым |
ofType(type) | проверяет, принадлежит ли значение типу |
match { it.startsWith("string") } | совпадает через мимолетный предикат |
coMatch { it.startsWith("string") } | совпадения с помощью предиката прошедшего коратика |
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() | захватывает лямбду |
captureCoroutine() | захватывает кораку |
invoke(...) | вызывает соответствующий аргумент |
coInvoke(...) | вызывает соответствующий аргумент для коратики |
hint(cls) | Намекает на следующий тип возврата в случае, если он будет стерт |
anyVararg() | соответствует любым элементам в Vararg |
varargAny(matcher) | совпадает, если какой -либо элемент совпадает с маттером |
varargAll(matcher) | совпадает, если все элементы соответствуют совпадению |
any...Vararg() | соответствует любым элементам в Варарге (специфично для примитивного типа) |
varargAny...(matcher) | соответствует, если какой -либо элемент соответствует совпадению (специфичный для примитивного типа) |
varargAll...(matcher) | соответствует, если все элементы соответствуют сочетаниям (специфично для примитивного типа) |
Несколько специальных совпадений, доступных только в режиме проверки:
| Маттер | Описание |
|---|---|
withArg { code } | соответствует любому значению и позволяет выполнять какой -то код |
withNullableArg { code } | соответствует любому нулевому значению и позволяет выполнять какой -то код |
coWithArg { code } | Соответствует любому значению и позволяет выполнить код коратики |
coWithNullableArg { code } | соответствует любому нулевому значению и позволяет выполнить код коратики |
| Валидатор | Описание |
|---|---|
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 } | Укажите, что сопоставленные вызовы ответы с помощью кодового блока Coroutine answer scope |
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 } | Укажите, что сопоставленные вызовы ответы с помощью кодового блока Coroutine answer scope |
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() | Позвоните захваченной лямбде |
coroutine<...>().coInvoke() | Назовите захваченную Coroutine |
nothing | Нулевое значение для возврата nothing как ответа |
fieldValue | Документ на поле поддержки собственности |
fieldValueAny | Набор аксессуаров для поля поддержки собственности с Any? тип |
value | Значение устанавливается, поднимается на тот же тип, что и поле поддержки свойств |
valueAny | Значение установлено, с Any? тип |
callOriginal | вызывает исходную функцию |
| Параметр | Описание |
|---|---|
position | позиция аргумента в массиве Vararg |
nArgs | Общее количество аргументов в массиве Vararg |
Вы также можете поддержать этот проект, став спонсором. Ваш логотип будет отображаться здесь со ссылкой на ваш сайт.
Спасибо всем нашим покровителям!
Этот проект существует благодаря всем людям, которые вносят свой вклад.
Чтобы задать вопросы, пожалуйста, используйте переполнение стека или дрессировщик.
Чтобы сообщить об ошибках, пожалуйста, используйте проект GitHub.