Todo lo que necesita para comenzar es solo agregar una dependencia a la biblioteca MockK .
| Acercarse | Instrucción |
|---|---|
![]() | testimonio "io.mockk: mockk: $ {MockKversion}"
|
(Kotlin DSL) | Testimplementation ("io.mockk: mockk: $ {MockKversion}") |
![]() | <pendencia>
<MoupRoD> io.mockk </groupid>
<ArFactId> Mockk-JVM </artifactId>
<versión> $ {MockKVersion} </versever>
<cope> test </cope>
</pendency>
|
Testemplemento "io.mockk: mockk-android: $ {MockKversion}"
Testemplemento "io.mockk: mockk-agent: $ {MockKversion}"
| |
AndroidTestimplementation "io.mockk: Mockk-Android: $ {MockKversion}"
AndroidTestimplementation "io.mockk: mockk-agent: $ {MockKversion}"
|
Ejemplo más simple. Por defecto, los simulacros son estrictos, por lo que debe proporcionar algún comportamiento.
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)Consulte la sección "Características" a continuación para ver ejemplos más detallados.
De la versión 1.13.0 Mockk admite Kotlin 1.4 y superior
mockkStatic no puede funcionar en JDK 16+; InaccessibleObjectException / IllegalAccessException : lea más aquíTabla de contenido:
Compruebe la serie de artículos "burlarse no es ciencia espacial" en KT. Academia que describe a Mockk de los conceptos básicos de burlarse de todas las características avanzadas.
Lo esencial
Verificación de comportamiento y comportamiento esperado
Características de Mockk
Características avanzadas de Mockk
Prueba de Quarkus con Kotlin, Junit y Mockk
Desentrañando la magia negra de Mockk (en, traducción)
Guía de Mockk
"Prueba de la unidad de Kotlin con Mockk" por Marco Cattaneo
(Video) Use Verify en Mockk para validar las llamadas de función en el objeto Mocked
Pruebas con Mockk pagado en raywenderlich.com
TDD para Android Video Tutorial Parte 1, Parte 2 por Ryan Kay
(Video) Desarrollador de Android en vivo Codificación #13: Pruebas unitarias con Mockk, coroutinas, desarrollo impulsado por las pruebas
Kotlinconf 2018 - Mejores prácticas para pruebas unitarias en Kotlin por Philipp Hauer
Kotlin-Fullstack-Sample utiliza el proyecto Mockk cubierto con pruebas
Artículo de Dzone
Artículo de Habrahabr (Ru)
Burlarse en Kotlin con Mockk - Yannick de Turck
Puede usar anotaciones para simplificar la creación de objetos simulados:
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
}
} La inyección primero intenta coincidir con las propiedades por nombre, luego por clase o superclase. Verifique el parámetro lookupType para la personalización.
Las propiedades se inyectan incluso si se aplica private . Los constructores para la inyección se seleccionan desde el mayor número de argumentos hasta el más bajo.
@InjectMockKs por defecto solo lateinit var S o var s que no están asignados. Para cambiar esto, use overrideValues = true . Esto asignaría el valor incluso si ya se inicializa de alguna manera. Para inyectar val s, use injectImmutable = true . Para una notación más corta, use @OverrideMockKs que hace lo mismo que @InjectMockKs de forma predeterminada, pero enciende estas dos banderas.
JUnit 4 expone una API basada en reglas para permitir algo de automatización después del ciclo de vida de la prueba. Mockk incluye una regla que usa esto para configurar y derribar sus simulacros sin necesidad de llamar manualmente MockKAnnotations.init(this) . Ejemplo:
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
}
} En JUnit5 puede usar MockKExtension para inicializar sus simulacros.
@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
}
} Además, agrega la posibilidad de usar @MockK y @RelaxedMockK en los parámetros de la función de prueba:
@Test
fun calculateAddsValues1 (@MockK car1 : Car , @RelaxedMockK car2 : Car ) {
// ... use car1 and car2
} Finalmente, esta extensión llamará unmockkAll y clearAllMocks en una devolución de llamada @AfterAll , asegurando que su entorno de prueba esté limpio después de cada ejecución de la clase de prueba. Puede deshabilitar este comportamiento agregando la anotación de @MockKExtension.KeepMocks a su clase o globalmente configurando el mockk.junit.extension.keepmocks=true . (Ya que v1.13.11) alternativamente, dado que clearAllMocks de forma predeterminada ( currentThreadOnly=false ) no es segura de hilo, si necesita ejecutar la prueba en paralelo, puede agregar el MockKExtension.RequireParallelTesting annotation a su clase o establecer el mockk.junit.extension.requireParallelTesting=true a desacable al llamar en el @AfterAll . Si se llama explícitamente clearAllMocks , puede suministrar clearAllMocks(currentThreadOnly = true) para que solo borre los simulacros creados dentro del mismo hilo (desde v1.13.12).
Puede asegurarse de que todos los métodos retirados se verifiquen realmente anotando su clase de prueba con @MockKExtension.ConfirmVerification .
Esto llamará internamente confirmVerified en todos los simulacros después de cada prueba, para asegurarse de que no haya torce innecesarios.
Tenga en cuenta que este comportamiento puede no funcionar como se esperaba cuando se ejecuta pruebas en su IDE, ya que se gradúa quien se encarga de manejar la excepción que se lanza cuando estas llamadas confirmVerified fallan.
Puede asegurarse de que todos los métodos retirados sean útiles, utilizados al menos una vez, al anotando también su clase de prueba con @MockKExtension.CheckUnnecessaryStub .
Esto llamará internamente checkUnnecessaryStub en todos los simulacros después de cada prueba, para asegurarse de que no haya agitados innecesarios.
Los espías le permiten mezclar simulacros y objetos reales.
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)Nota 1: El objeto espía es una copia del objeto pasado. Nota 2: Hay un problema conocido si se usa un espía con una función de suspensión: #554
Un relaxed mock es el simulacro que devuelve algún valor simple para todas las funciones. Esto le permite omitir el comportamiento de especificación para cada caso, sin dejar de robar las cosas que necesita. Para los tipos de referencia, se devuelven simulacros encadenados.
val car = mockk< Car >(relaxed = true )
car.drive( Direction . NORTH ) // returns null
verify { car.drive( Direction . NORTH ) }
confirmVerified(car)Nota: La burla relajada funciona mal con tipos de retorno genérico. Una excepción de clase de clase generalmente se arroja en este caso. Opta por retrasarse manualmente en el caso de un tipo de retorno genérico.
Solución:
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() A veces, debe eliminar algunas funciones, pero aún así llamar al método real en otros, o en argumentos específicos. Esto es posible pasando callOriginal() a answers , que funciona para simulacros relajados y no relajados.
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 Si desea que las funciones de retorno Unit se relajen, puede usar relaxUnitFun = true como argumento para la función mockk , la anotación @MockK o la función MockKAnnotations.init .
Función:
mockk< ClassBeingMocked >(relaxUnitFun = true )Anotación:
@MockK(relaxUnitFun = true )
lateinit var mock1 : ClassBeingMocked
init {
MockKAnnotations . init ( this )
}MockKannotations.Init:
@MockK
lateinit var mock2 : ClassBeingMocked
init {
MockKAnnotations . init ( this , relaxUnitFun = true )
}Los objetos se pueden convertir en simulacros de la siguiente manera:
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 )) Para volver, use unmockkObject o unmockkAll (más destructivo: objeto de cancelas, simulacros estáticos y constructores)
@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()
}A pesar de las restricciones del lenguaje Kotlin, puede crear nuevas instancias de objetos si es necesario mediante la lógica de prueba:
val newObjectMock = mockk< ObjBeingMocked >() A veces necesitas un simulacro de una clase arbitraria. Use mockkClass en esos casos.
val car = mockkClass( Car :: class )
every { car.drive( Direction . NORTH ) } returns Outcome . OK
car.drive( Direction . NORTH ) // returns OK
verify { car.drive( Direction . NORTH ) } Las enumeras se pueden burlarse usando 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)A veces, especialmente en el código que no posee, debe burlarse de los objetos recién creados. Para este propósito, se proporcionan las siguientes construcciones:
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 ) } La idea básica es que justo después de que se ejecuta el constructor de la clase burlada (cualquiera de ellos), los objetos se convierten en un constructed mock .
El comportamiento burlón de este simulacro está conectado al prototype mock denotado por anyConstructed<MockCls>() .
Hay una instancia por clase de tal prototype mock . La grabación de llamadas también ocurre con el prototype mock .
Si no se especifica ningún comportamiento para la función, entonces se ejecuta la función original.
En caso de que una clase tenga más de un constructor, cada uno puede burlarse por separado:
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 )
} Tenga en cuenta que en este caso, se crea un prototype mock para cada conjunto de combaters de argumentos pasados a constructedWith .
Puede mezclar tanto argumentos regulares como a Matchers:
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)Puedes agitar las cadenas de llamadas:
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) Nota: Si el tipo de retorno de la función es genérico, la información sobre el tipo real se ha ido.
Para hacer que las llamadas encadenadas funcionen, se requiere información adicional.
La mayoría de las veces el marco captará la excepción del elenco y hará autohinting .
En el caso de que se requiere explícitamente, use hint antes de hacer la próxima llamada.
every { obj.op2( 1 , 2 ).hint( Int :: class ).op1( 3 , 4 ) } returns 5De la versión 1.9.1 Los simulacros pueden estar encadenados en jerarquías:
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 "
}
}
)
} Puede capturar un argumento a un CapturingSlot o MutableList .
CapturingSlot generalmente se crea a través de slot<T : Any?>() Y es posible capturar tipos anulables y no anulables. MutableList está destinado a capturar múltiples valores durante la prueba.
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) Puede verificar el recuento de llamadas con al atLeast , atMost o exactly parámetros:
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) O puede usar 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 verifica que todas las llamadas ocurrieron sin verificar su pedido.verifySequence verifica que las llamadas ocurrieron en una secuencia específica.verifyOrder verifica que las llamadas ocurrieron en un orden específico.wasNot Called verifica que el simulacro (o la lista de simulacros) no se llamó en absoluto. 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) Para verificar que todas las llamadas se verificaran por verify... Construcciones, puede usar confirmVerified :
confirmVerified(mock1, mock2) No tiene mucho sentido usarlo para verifySequence y verifyAll , ya que estos métodos de verificación ya cubren todas las llamadas con verificación.
Lanzará una excepción si quedan algunas llamadas sin verificación.
Algunas llamadas pueden excluirse de esta confirmación, consulte la siguiente sección para obtener más detalles.
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 verificationDebido a que el código de prueba limpio y mantenible requiere un código innecesario cero, puede asegurarse de que no haya trozos innecesarios.
checkUnnecessaryStub(mock1, mock2)Lanzará una excepción si hay algunas llamadas declaradas en los simulacros que no utilizan el código probado. Esto puede suceder si ha declarado algunos trozos realmente innecesarios o si el código probado no llama uno esperado.
Para excluir las llamadas sin importancia de ser registradas, puede usar excludeRecords :
excludeRecords { mock.operation(any(), 5 ) } Todas las llamadas coincidentes se excluirán de la grabación. Esto puede ser útil si está utilizando una verificación exhaustiva: verifyAll , verifySequence o 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) Para verificar las operaciones concurrentes, puede usar timeout = xxx :
mockk< MockCls > {
every { sum( 1 , 2 ) } returns 4
Thread {
Thread .sleep( 2000 )
sum( 1 , 2 )
}.start()
verify(timeout = 3000 ) { sum( 1 , 2 ) }
}Esto esperará hasta uno de los dos estados siguientes: se pasa la verificación o se alcanza el tiempo de espera.
Si una función devuelve Unit , puede usar la construcción 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 )
} Otras formas de escribir justRun { obj.sum(any(), 3) } :
every { obj.sum(any(), 3) } just Runsevery { obj.sum(any(), 3) } returns Unitevery { obj.sum(any(), 3) } answers { Unit }Para simular la corutina, debe agregar otra dependencia a la biblioteca de soporte.
| Graduarse |
|---|
testimonio "org.jetbrains.kotlinx: kotlinx-coroutines-core: xx" |
| Aturdir |
|---|
<pendencia>
<MoupRoD> org.jetbrains.kotlinx </groupid>
<artifactID> kotlinx-corutines-core </artifactid>
<versión> xx </versión>
<cope> test </cope>
</pendency> |
Luego puede usar coEvery , coVerify , coMatch , coAssert , coRun , coAnswers o coInvoke para simularse de suspensión de funciones.
val car = mockk< Car >()
coEvery { car.drive( Direction . NORTH ) } returns Outcome . OK
car.drive( Direction . NORTH ) // returns OK
coVerify { car.drive( Direction . NORTH ) } Y para simular una función suspend nunca devuelto, puede usar 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
}Nota: Hay un problema conocido si se usa un espía con una función de suspensión: #554
Kotlin le permite declarar funciones que no pertenecen a ninguna clase u objeto, llamadas funciones de nivel superior. Estas llamadas se traducen a métodos estáticos en entornos jvm , y se genera una clase Java especial para mantener las funciones. Estas funciones de nivel superior se pueden burlarse utilizando mockkStatic . Solo necesita importar la función y aprobar una referencia como argumento:
import com.cars.buildCar
val testCar = Car ()
mockkStatic(::buildCar)
every { buildCar() } returns testCar
assertEquals(testCar, buildCar())
verify { buildCar() } Burlarse de una función borrará cualquier simulacro existente de otras funciones declaradas en el mismo archivo, equivalente a llamar clearStaticMockk en la clase de enclose generada.
Hay tres tipos de función de extensión en Kotlin:
Para un objeto o una clase, puede burlarse de las funciones de extensión simplemente creando un mockk regular:
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()
}
} Para burlarse de las funciones de extensión de todo el módulo, debe construir mockkStatic(...) con el nombre de clase del módulo como argumento. Por ejemplo, "PKG.FILEKT" para el File.kt módulo.kt en el paquete 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()
} En entornos jvm puede reemplazar el nombre de clase con una referencia de función:
mockkStatic( Obj ::extensionFunc) Tenga en cuenta que esto se burlará de toda la clase pkg.FileKt , y no solo extensionFunc .
Esta sintaxis también se aplica para las propiedades de extensión:
val Obj .squareValue get() = value * value
mockkStatic( Obj ::squareValue) Si se usa @JvmName , especifíquelo como un nombre de clase.
Khttp.kt:
@file:JvmName( " KHttp " )
package khttp
// ... KHttp code Código de prueba:
mockkStatic( " khttp.KHttp " ) A veces necesitas saber un poco más para burlarse de una función de extensión. Por ejemplo, la función de extensión File.endsWith() tiene un classname totalmente impredecible:
mockkStatic( " kotlin.io.FilesKt__UtilsKt " )
every { File ( " abc " ).endsWith(any< String >()) } returns true
println ( File ( " abc " ).endsWith( " abc " )) Este es el comportamiento estándar de Kotlin que puede ser impredecible. Use Tools -> Kotlin -> Show Kotlin Bytecode o verificar archivos .class en el archivo JAR para detectar dichos nombres.
De la versión 1.9.1, es posible un manejo de vararg más extendido:
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 )) // 6Si necesita burlarse de las funciones privadas, puede hacerlo a través de una llamada dinámica.
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 " ]()
} Si desea verificar llamadas privadas, debe crear un spyk con recordPrivateCalls = true
Además, una sintaxis más detallada le permite obtener y establecer propiedades, combinadas con las mismas llamadas dinámicas:
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 " ) } Puede acceder a los campos de respaldo a través del fieldValue y value de uso para el valor que se establece.
Nota: En los ejemplos a continuación, usamos propertyType para especificar el tipo de fieldValue . Esto es necesario porque es posible capturar el tipo automáticamente para el Getter. Use nullablePropertyType para especificar un tipo anulable.
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
}Agregar comportamientos adicionales a través de interfaces y retirarlos:
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() Nada especial aquí. Si no tiene una función que no devuelve Nothing :
fun quit ( status : Int ): Nothing {
exitProcess(status)
}Entonces, por ejemplo, puede lanzar una excepción como comportamiento:
every { quit( 1 ) } throws Exception ( " this is a test " ) Un simulacro de alcance es un simulacro que se descifra automáticamente después de que el bloque de código se ha ejecutado como parámetro. Puede usar las funciones mockkObject , mockkStatic y 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 ))
} Una forma muy simple de crear nuevos Matchers es uniendo una función a MockKMatcherScope o MockKVerificationScope y utilizando la función match :
fun MockKMatcherScope. seqEq ( seq : Sequence < String >) = match< Sequence < String >> {
it.toList() == seq.toList()
} También es posible crear Matchers más avanzados mediante la implementación de la interfaz Matcher .
Ejemplo de un combate personalizado que compara la lista sin orden:
@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))Ejemplo usando la reflexión para burlarse de todos los métodos en un objeto estilo constructor
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
}
}
}Para ajustar los parámetros a nivel mundial, hay algunas configuraciones que puede especificar en un archivo de recursos.
Cómo usar:
io/mockk/settings.properties en src/main/resources . relaxed =true|false
relaxUnitFun =true|false
recordPrivateCalls =true|false
stackTracesOnVerify =true|false
stackTracesAlignment =left|center stackTracesAlignment determina si se debe alinear las trazas de pila al centro (predeterminado) o al izquierdo (más consistente con las pilas JVM habituales).
Aquí hay algunas tablas para ayudarlo a dominar el DSL.
| Función | Descripción |
|---|---|
mockk<T>(...) | construye un simulacro regular |
spyk<T>() | construye un espía utilizando el constructor predeterminado |
spyk(obj) | construye un espía copiando de obj |
slot | crea una ranura para capturar |
every | comienza un bloque de trozos |
coEvery | comienza un bloque de trozos para coroutinas |
verify | inicia un bloque de verificación |
coVerify | inicia un bloque de verificación para coroutinas |
verifyAll | inicia un bloque de verificación que debe incluir todas las llamadas |
coVerifyAll | inicia un bloque de verificación que debe incluir todas las llamadas para coroutinas |
verifyOrder | inicia un bloque de verificación que verifica el pedido |
coVerifyOrder | inicia un bloque de verificación que verifica el pedido de las coroutinas |
verifySequence | inicia un bloque de verificación que verifica si todas las llamadas se hicieron en una secuencia específica |
coVerifySequence | inicia un bloque de verificación que verifica si todas las llamadas se hicieron en una secuencia específica para las coroutinas |
excludeRecords | excluir algunas llamadas de ser grabadas |
confirmVerified | confirma que todas las llamadas grabadas fueron verificadas |
checkUnnecessaryStub | confirma que todas las llamadas grabadas se usan al menos una vez |
clearMocks | borra simulacros especificados |
registerInstanceFactory | le permite redefinir la forma de instanciación para cierto objeto |
mockkClass | construye un simulacro regular pasando la clase como parámetro |
mockkObject | convierte un objeto en un objeto simulado, o lo borra si ya se transformó |
unmockkObject | convierte un objeto simulado en un objeto normal |
mockkStatic | Hace un simulacro estático de una clase, o la aclara si ya se transformaba |
unmockkStatic | convierte un simulacro estático en una clase regular |
clearStaticMockk | borra un simulacro estático |
mockkConstructor | Hace que un constructor se burle de una clase, o lo aclara si ya se transformó |
unmockkConstructor | convierte un constructor simulado en una clase regular |
clearConstructorMockk | borra el constructor simulado |
unmockkAll | Desganza de objeto, estático y constructor |
clearAllMocks | borra las simulacros regulares, de objetos, estáticos y de constructor |
Por defecto, los argumentos simples coinciden usando eq()
| Pareja | Descripción |
|---|---|
any() | coincide con cualquier argumento |
any(Class) | coincide con cualquier argumento de la clase Give (para la burla reflexiva) |
allAny() | Matcher especial que usa any() en lugar de eq() para combaters que se proporcionan como argumentos simples |
isNull() | verifica si el valor es nulo |
isNull(inverse=true) | verifica si el valor no es nulo |
ofType(type) | verifica si el valor pertenece al tipo |
match { it.startsWith("string") } | coincidencias a través del predicado aprobado |
coMatch { it.startsWith("string") } | coincidencias a través del predicado de coroutine pasado |
matchNullable { it?.startsWith("string") } | coincide con el valor anulable a través del predicado pasado |
coMatchNullable { it?.startsWith("string") } | coincide con el valor anulable a través del predicado de coroutine pasado |
eq(value) | coincide si el valor es igual al valor proporcionado a través de la función deepEquals |
eq(value, inverse=true) | coincide si el valor no es igual al valor proporcionado a través de la función deepEquals |
neq(value) | coincide si el valor no es igual al valor proporcionado a través de la función deepEquals |
refEq(value) | coincide si el valor es igual al valor proporcionado a través de la comparación de referencia |
refEq(value, inverse=true) | coincide si el valor no es igual al valor proporcionado a través de la comparación de referencia |
nrefEq(value) | coincide si el valor no es igual al valor proporcionado a través de la comparación de referencia |
cmpEq(value) | coincide si el valor es igual al valor proporcionado a través de la función compareTo |
less(value) | coincide si el valor es menor que el valor proporcionado a través de la función compareTo |
more(value) | coincide si el valor es mayor que el valor proporcionado a través de la función compareTo |
less(value, andEquals=true) | coincide si el valor es menor o igual al valor proporcionado a través de la función compareTo |
more(value, andEquals=true) | coincide si el valor es más o igual al valor proporcionado a través de la función compareTo |
range(from, to, fromInclusive=true, toInclusive=true) | coincide si el valor está en el rango a través de la función compareTo |
and(left, right) | combina dos mates a través de una lógica y |
or(left, right) | Combina dos Matchers a través de una lógica o |
not(matcher) | niega la matcher |
capture(slot) | Captura un valor no anulable para un CapturingSlot |
captureNullable(slot) | Captura un valor anullable para un CapturingSlot |
capture(mutableList) | captura un valor para una lista |
captureNullable(mutableList) | captura un valor para una lista junto con valores nulos |
captureLambda() | captura una lambda |
captureCoroutine() | captura una coroutina |
invoke(...) | llama a un argumento coincidente |
coInvoke(...) | llama a un argumento coincidente para una coroutina |
hint(cls) | sugiere el siguiente tipo de devolución en caso de que se haya borrado |
anyVararg() | coincide con cualquier elemento en un vararg |
varargAny(matcher) | coincide si algún elemento coincide con la matcher |
varargAll(matcher) | coincidencias si todos los elementos coinciden con el matcher |
any...Vararg() | coincide con cualquier elemento en vararg (específico al tipo primitivo) |
varargAny...(matcher) | coincide si algún elemento coincide con el matcher (específico del tipo primitivo) |
varargAll...(matcher) | coinciden si todos los elementos coinciden con el matcher (específico del tipo primitivo) |
Algunos combaters especiales disponibles solo en modo de verificación:
| Pareja | Descripción |
|---|---|
withArg { code } | coincide con cualquier valor y permite ejecutar algún código |
withNullableArg { code } | coincide con cualquier valor anulable y permite ejecutar algún código |
coWithArg { code } | coincide con cualquier valor y permite ejecutar algún código de coroutine |
coWithNullableArg { code } | coincide con cualquier valor anulable y permite ejecutar algún código de coroutine |
| Validador | Descripción |
|---|---|
verify { mock.call() } | Hacer verificación desordenada de que se realizó una llamada |
verify(inverse=true) { mock.call() } | Verificación desordenada de que no se realizó una llamada |
verify(atLeast=n) { mock.call() } | Verificación desordenada de que se realizó una llamada al menos n veces |
verify(atMost=n) { mock.call() } | Verificación desordenada de que se realizó una llamada como máximo n veces |
verify(exactly=n) { mock.call() } | Verificación desordenada de que una llamada se realizó exactamente n veces |
verifyAll { mock.call1(); mock.call2() } | Verificación desordenada de que solo se ejecutaron las llamadas especificadas para los simulacros mencionados |
verifyOrder { mock.call1(); mock.call2() } | Verificar que la secuencia de llamadas fue una tras otra |
verifySequence { mock.call1(); mock.call2() } | Verificar que solo se ejecutó la secuencia especificada de llamadas para los simulacros mencionados |
verify { mock wasNot Called } | Verificación de que no se llamó a un simulacro |
verify { listOf(mock1, mock2) wasNot Called } | Verificación de que no se llamaba una lista de simulacros |
Una respuesta puede ser seguida por una o más respuestas adicionales.
| Respuesta | Descripción |
|---|---|
returns value | especificar que la llamada coincidente devuelve un valor especificado |
returnsMany list | Especifique que la llamada coincidente devuelva un valor de la lista, con llamadas posteriores que devuelven el siguiente elemento |
returnsArgument(n) | especificar que la llamada coincidente devuelve el enésimo argumento de esa llamada |
throws ex | Especifique que la llamada coincidente lanza una excepción |
throwsMany list | Especifique que la llamada coincidente arroja una excepción de la lista, con llamadas posteriores que lanzan la próxima excepción |
answers { code } | Especifique que las respuestas de llamada coincidentes con un bloque de código alcanzado con answer scope |
coAnswers { code } | Especifique que las respuestas de llamada coincidentes con un bloque de código Coroutine con answer scope |
answers answerObj | Especifique que la llamada coincida responda con un objeto de respuesta |
answers { nothing } | Especifique que las respuestas de llamada coincidentes nulas |
just Runs | Especifique que la llamada coincidente sea la unidad de regreso (devuelve nulo) |
just Awaits | Especifique que la llamada coincidente nunca regrese (disponible desde V1.13.3) |
propertyType Class | especificar el tipo de accesor de campo de respaldo |
nullablePropertyType Class | Especifique el tipo de accesor de campo de respaldo como un tipo anulable |
Se devuelve una siguiente respuesta en cada llamada consecuente y se persiste el último valor. Así que esto es similar a la semántica returnsMany .
| Respuesta adicional | Descripción |
|---|---|
andThen value | especificar que la llamada coincidente devuelve un valor especificado |
andThenMany list | Especifique que la llamada coincidente devuelva un valor de la lista, con llamadas posteriores que devuelven el siguiente elemento |
andThenThrows ex | Especifique que la llamada coincidente lanza una excepción |
andThenThrowsMany ex | Especifique que la llamada coincidente arroja una excepción de la lista, con llamadas posteriores que lanzan la próxima excepción |
andThen { code } | Especifique que las respuestas de llamada coincidentes con un bloque de código alcanzado con answer scope |
coAndThen { code } | Especifique que las respuestas de llamada coincidentes con un bloque de código Coroutine con answer scope |
andThenAnswer answerObj | Especifique que la llamada coincida responda con un objeto de respuesta |
andThen { nothing } | Especifique que las respuestas de llamada coincidentes nulas |
andThenJust Runs | Especifique que la llamada coincidente sea la unidad de regreso (disponible desde v1.12.2) |
andThenJust Awaits | Especifique que la llamada coincidente nunca regrese (disponible desde v1.13.3) |
| Parámetro | Descripción |
|---|---|
call | un objeto de llamada que consiste en una invocación y una matcher |
invocation | contiene información sobre la función real invocada |
matcher | Contiene información sobre el combate utilizado para igualar la invocación |
self | referencia a la invocación de objeto realizada |
method | referencia a la invocación de funciones realizada |
args | referencia a los argumentos de invocación |
nArgs | Número de argumentos de invocación |
arg(n) | nth argumento |
firstArg() | primer argumento |
secondArg() | segundo argumento |
thirdArg() | tercer argumento |
lastArg() | último argumento |
captured() | El último elemento en la lista por conveniencia al capturar una lista |
lambda<...>().invoke() | Llame a la lambda capturada |
coroutine<...>().coInvoke() | Llame al Coroutine capturado |
nothing | valor nulo para devolver nothing como respuesta |
fieldValue | accesor del campo de respaldo de propiedad |
fieldValueAny | ¿Accesor al campo de respaldo de propiedad con Any? tipo |
value | Valor que se establece, lanzado al mismo tipo que el campo de respaldo de la propiedad |
valueAny | Valor que se establece, ¿con Any? tipo |
callOriginal | Llama a la función original |
| Parámetro | Descripción |
|---|---|
position | la posición de un argumento en una matriz de vararg |
nArgs | Recuento general de argumentos en una matriz de vararg |
También puede apoyar este proyecto convirtiéndose en patrocinador. Su logotipo aparecerá aquí con un enlace a su sitio web.
¡Gracias a todos nuestros patrocinadores!
Este proyecto existe gracias a todas las personas que contribuyen.
Para hacer preguntas, use el desbordamiento de la pila o Gitter.
Para informar errores, utilice el proyecto GitHub.