Tudo o que você precisa para começar é apenas adicionar uma dependência à biblioteca MockK .
| Abordagem | Instrução |
|---|---|
![]() | Testemplurações "io.mockk: mockk: $ {mockkversion}"
|
(Kotlin DSL) | testimplementation ("io.mockk: mockk: $ {mockkversion}") |
![]() | <pendência>
<GrupidId> io.mockk </foupid>
<TarfactId> mockk-jvm </stifactId>
<versão> $ {mockkversion} </versão>
<cope> teste </cope>
</dependency>
|
Testemplementação "io.mockk: mockk-android: $ {mockkversion}"
Testemplementação "io.mockk: mockk-agent: $ {mockkversion}"
| |
Androidtestimplementation "io.mockk: mockk-android: $ {mockkversion}"
Androidtestimplementation "io.mockk: mockk-agent: $ {mockkversion}"
|
Exemplo mais simples. Por padrão, zombares são rigorosas, então você precisa fornecer algum comportamento.
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 a seção "Recursos" abaixo para obter exemplos mais detalhados.
Da versão 1.13.0 Mockk suporta Kotlin 1.4 e superior
mockkStatic pode não funcionar no JDK 16+; InaccessibleObjectException / IllegalAccessException : Leia mais aquiÍndice:
Verifique a série de artigos "Zocking não é ciência do foguete" na KT. Academia descrevendo o Mockk do básico de zombar para a descrição de todos os recursos avançados.
Básico
Comportamento esperado e verificação de comportamento
Recursos de mockk
Recursos avançados da Mockk
Testando Quarkus com Kotlin, Junit e Mockk
Desvendando a magia negra do Mockk (en, tradução)
Guia do Mockk
“Teste de unidade Kotlin com Mockk”, de Marco Cattaneo
(Vídeo) Use Verifique no Mockk para validar chamadas de função em objeto ridículo
Testando com o curso pago da Mockk em Raywenderlich.com
Tutorial em vídeo do TDD para Android Parte 1, parte 2 por Ryan Kay
(Vídeo) Desenvolvedor Android Codificação ao vivo #13: Teste de unidade com Mockk, Coroutines, Desenvolvimento Divido de Teste
Kotlinconf 2018 - Melhores práticas para testes de unidade em Kotlin por Philipp Hauer
Kotlin-Fullstack-Sple usa o projeto Mockk coberto com testes
Artigo de Dzone
Artigo de Habrahabr (RU)
Zombando de Kotlin com Mockk - Yannick de Turck
Você pode usar anotações para simplificar a criação 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
}
} A injeção primeiro tenta corresponder às propriedades por nome e depois por classe ou superclass. Verifique o parâmetro lookupType para obter personalização.
As propriedades são injetadas, mesmo que private sejam aplicadas. Os construtores de injeção são selecionados do maior número de argumentos para a menor.
@InjectMockKs por padrão injeta apenas VARS ou var lateinit var que não são atribuídos. Para alterar isso, use overrideValues = true . Isso atribuiria o valor mesmo que já seja inicializado de alguma forma. Para injetar val s, use injectImmutable = true . Para uma notação mais curta, use @OverrideMockKs que faz o mesmo que @InjectMockKs por padrão, mas liga essas duas bandeiras.
O JUNIT 4 expõe uma API baseada em regras para permitir alguma automação após o ciclo de vida do teste. O Mockk inclui uma regra que usa isso para configurar e derrubar suas zombarias sem precisar chamar manualmente MockKAnnotations.init(this) . Exemplo:
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
}
} No JUNIT5, você pode usar MockKExtension para inicializar suas zombarias.
@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
}
} Além disso, adiciona a possibilidade de usar @MockK e @RelaxedMockK nos parâmetros da função de teste:
@Test
fun calculateAddsValues1 (@MockK car1 : Car , @RelaxedMockK car2 : Car ) {
// ... use car1 and car2
} Por fim, essa extensão chamará unmockkAll e clearAllMocks em um retorno de chamada @AfterAll , garantindo que seu ambiente de teste esteja limpo após cada execução da classe de teste. Você pode desativar esse comportamento adicionando a anotação @MockKExtension.KeepMocks à sua classe ou globalmente, definindo o mockk.junit.extension.keepmocks=true . (Como v1.13.11) Como alternativa, como clearAllMocks por padrão ( currentThreadOnly=false ) não é seguro, se você precisar executar o teste em paralelo, poderá adicionar o MockKExtension.RequireParallelTesting ANOTAÇÃO DO TRUSO DO @AfterAll mockk.junit.extension.requireParallelTesting=true . Se clearAllMocks for chamado explicitamente, você poderá fornecer clearAllMocks(currentThreadOnly = true) para que ele apenas limpe as zombarias criadas no mesmo thread (desde v1.13.12).
Você pode garantir que todos os métodos Stubbed sejam realmente verificados anotando também sua classe de teste com @MockKExtension.ConfirmVerification .
Isso ligará internamente confirmVerified em todas as manchas após cada teste, para garantir que não haja malhas desnecessárias.
Observe que esse comportamento pode não funcionar conforme o esperado ao executar os testes em seu IDE, pois é Gradle quem cuida do manuseio da exceção que está sendo lançado quando essas chamadas confirmVerified falharem.
Você pode garantir que todos os métodos Stubbed sejam úteis - usados pelo menos uma vez - anotendo também sua classe de teste com @MockKExtension.CheckUnnecessaryStub .
Isso chamará internamente checkUnnecessaryStub em todas as zombarias após cada teste, para garantir que não haja malhas desnecessárias.
Os espiões permitem que você misture maquetes e objetos reais.
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: O objeto Spy é uma cópia do objeto passado. Nota 2: Há um problema conhecido se estiver usando um espião com uma função de suspensão: #554
Uma relaxed mock é a simulação que retorna algum valor simples para todas as funções. Isso permite pular o comportamento especificando para cada caso, enquanto ainda machucava as coisas que você precisa. Para tipos de referência, zombares acorrentadas são devolvidas.
val car = mockk< Car >(relaxed = true )
car.drive( Direction . NORTH ) // returns null
verify { car.drive( Direction . NORTH ) }
confirmVerified(car)NOTA: A zombaria relaxada está funcionando mal com os tipos de retorno genéricos. Uma exceção de elenco de classe geralmente é lançada neste caso. Opte por Stubbing manualmente no caso de um tipo de retorno genérico.
Solução alternativa:
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() Às vezes, você precisa extrair algumas funções, mas ainda assim chama o método real de outros ou de argumentos específicos. Isso é possível passando callOriginal() para answers , que funciona para manchas relaxadas e não relaxadas.
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 Se você deseja que as funções de retorno Unit sejam relaxadas, você pode usar relaxUnitFun = true como um argumento para a função mockk , anotação @MockK ou função MockKAnnotations.init .
Função:
mockk< ClassBeingMocked >(relaxUnitFun = true )Anotação:
@MockK(relaxUnitFun = true )
lateinit var mock1 : ClassBeingMocked
init {
MockKAnnotations . init ( this )
}Mockkannotações.init:
@MockK
lateinit var mock2 : ClassBeingMocked
init {
MockKAnnotations . init ( this , relaxUnitFun = true )
}Os objetos podem ser transformados em zombares da seguinte maneira:
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 voltar de volta, use unmockkObject ou unmockkAll (mais destrutivo: cancela o objeto, manchas estáticas e construtores)
@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()
}Apesar das restrições da língua Kotlin, você pode criar novas instâncias de objetos, se necessário, testando a lógica:
val newObjectMock = mockk< ObjBeingMocked >() Às vezes você precisa de uma aula arbitrária. Use mockkClass nesses 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 ) } As enumes podem ser ridicularizadas 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)Às vezes, especialmente no código que você não possui, você precisa zombar de objetos recém -criados. Para esse fim, são fornecidos os seguintes construções:
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 ) } A idéia básica é que, logo após a execução do construtor da classe zombada (qualquer um deles), os objetos se tornam uma constructed mock .
O comportamento zombeteiro de tal zombaria está conectado ao prototype mock especial de indicado por anyConstructed<MockCls>() .
Há uma instância por classe desse prototype mock . A gravação de chamadas também acontece com o prototype mock .
Se nenhum comportamento para a função for especificado, a função original será executada.
Caso uma aula tenha mais de um construtor, cada um pode ser ridicularizado separadamente:
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 )
} Observe que, nesse caso, um prototype mock é criado para todos os conjuntos de correspondências de argumentos passados para constructedWith .
Você pode misturar argumentos regulares e matcores:
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)Você pode extrair cadeias de chamadas:
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: Se o tipo de retorno da função for genérico, as informações sobre o tipo real desapareceram.
Para fazer as chamadas encadeadas funcionarem, são necessárias informações adicionais.
Na maioria das vezes, a estrutura captará a exceção do elenco e fará autohinting .
No caso de ser explicitamente necessário, use hint antes de fazer a próxima chamada.
every { obj.op2( 1 , 2 ).hint( Int :: class ).op1( 3 , 4 ) } returns 5A partir da versão 1.9.1, as zombares podem ser acorrentadas em hierarquias:
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 "
}
}
)
} Você pode capturar um argumento para um CapturingSlot ou MutableList .
CapturingSlot é geralmente criado via slot<T : Any?>() E é possível capturar tipos anuláveis e não anuláveis. MutableList destina -se a capturar vários valores durante o teste.
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) Você pode verificar a contagem de chamadas com o atLeast atMost ou 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) Ou você pode 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 se verifica se todas as chamadas aconteceram sem verificar sua ordem.verifySequence verifica que as chamadas ocorreram em uma sequência especificada.verifyOrder verifica se as chamadas ocorreram em uma ordem específica.wasNot Called verifica que a simulação (ou a lista de zombarias) não foi chamada. 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 se todas as chamadas foram verificadas por verify... Construções, você pode usar confirmVerified :
confirmVerified(mock1, mock2) Não faz muito sentido usá -lo para verifySequence e verifyAll , pois esses métodos de verificação já cobrem exaustivamente todas as chamadas com verificação.
Ele fará uma exceção se houver algumas chamadas sem verificação.
Algumas chamadas podem ser excluídas desta confirmação, verifique a próxima seção para obter mais detalhes.
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 verificationComo o código de teste limpo e sustentável requer zero código desnecessário, você pode garantir que não haja stubs desnecessários.
checkUnnecessaryStub(mock1, mock2)Ele fará uma exceção se houver algumas chamadas declaradas nas manchas que não são usadas pelo código testado. Isso pode acontecer se você declarou alguns stubs realmente desnecessários ou se o código testado não chamar um esperado.
Para excluir chamadas sem importância de serem gravadas, você pode usar excludeRecords :
excludeRecords { mock.operation(any(), 5 ) } Todas as chamadas correspondentes serão excluídas da gravação. Isso pode ser útil se você estiver usando a verificação exaustiva: verifyAll , verifySequence ou 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 as operações simultâneas, você pode 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 ) }
}Isso esperará até um dos dois estados seguintes: a verificação é passada ou o tempo limite será atingido.
Se uma função retornar Unit , você poderá usar o justRun Construct:
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 )
} Outras maneiras de escrever 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 zombar dos coroutinas, você precisa adicionar outra dependência à biblioteca de suporte.
| Gradle |
|---|
Testemplementação "Org.Jetbrains.kotlinx: Kotlinx-coroutines-core: xx" |
| Maven |
|---|
<pendência>
<GrupidId> org.jetbrains.kotlinx </foupid>
<TarfactId> kotlinx-coroutines-core </stifactId>
<versão> xx </versão>
<cope> teste </cope>
</dependency> |
Em seguida, você pode usar coEvery , coVerify , coMatch , coAssert , coRun , coAnswers ou coInvoke para zombar das funções de suspensão.
val car = mockk< Car >()
coEvery { car.drive( Direction . NORTH ) } returns Outcome . OK
car.drive( Direction . NORTH ) // returns OK
coVerify { car.drive( Direction . NORTH ) } E para simular uma função suspend nunca retornante, você pode 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: Há um problema conhecido se estiver usando um espião com uma função de suspensão: #554
Kotlin permite que você declare funções que não pertencem a nenhuma classe ou objeto, chamadas funções de nível superior. Essas chamadas são traduzidas para métodos estáticos em ambientes jvm , e uma classe Java especial é gerada para manter as funções. Essas funções de nível superior podem ser ridicularizadas usando mockkStatic . Você só precisa importar a função e passar uma referência como argumento:
import com.cars.buildCar
val testCar = Car ()
mockkStatic(::buildCar)
every { buildCar() } returns testCar
assertEquals(testCar, buildCar())
verify { buildCar() } A zombaria de uma função limpará qualquer zombaria existente de outras funções declaradas no mesmo arquivo, equivalente a chamar clearStaticMockk na classe Gerated Encloking.
Existem três tipos de função de extensão em Kotlin:
Para um objeto ou aula, você pode zombar das funções de extensão apenas criando um 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 zombar das funções de extensão em todo o módulo, você precisa criar mockkStatic(...) com o nome da classe do módulo como um argumento. Por exemplo, "pkg.filekt" para módulo File.kt no pacote 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()
} Nos ambientes jvm você pode substituir o nome da classe por uma referência de função:
mockkStatic( Obj ::extensionFunc) Observe que isso zombará de toda a classe pkg.FileKt , e não apenas extensionFunc .
Esta sintaxe também se aplica às propriedades de extensão:
val Obj .squareValue get() = value * value
mockkStatic( Obj ::squareValue) Se @JvmName for usado, especifique -o como um nome de classe.
Khttp.kt:
@file:JvmName( " KHttp " )
package khttp
// ... KHttp code Código de teste:
mockkStatic( " khttp.KHttp " ) Às vezes, você precisa saber um pouco mais para zombar de uma função de extensão. Por exemplo, a função de extensão File.endsWith() tem um classname totalmente imprevisível:
mockkStatic( " kotlin.io.FilesKt__UtilsKt " )
every { File ( " abc " ).endsWith(any< String >()) } returns true
println ( File ( " abc " ).endsWith( " abc " )) Esse é o comportamento padrão de Kotlin que pode ser imprevisível. Use Tools -> Kotlin -> Show Kotlin Bytecode ou verificar arquivos .class no arquivo jar para detectar esses nomes.
A partir da versão 1.9.1, é possível um manuseio de vararja mais estendido:
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 )) // 6Se você precisar zombar de funções privadas, pode fazê -lo através de uma chamada 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 " ]()
} Se você deseja verificar chamadas particulares, crie um spyk com recordPrivateCalls = true
Além disso, uma sintaxe mais detalhada permite obter e definir propriedades, combinadas com as mesmas chamadas 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 " ) } Você pode acessar os campos de apoio via fieldValue e usar value para o valor definido.
NOTA: Nos exemplos abaixo, usamos propertyType para especificar o tipo de fieldValue . Isso é necessário porque é possível capturar o tipo automaticamente para o getter. Use nullablePropertyType para especificar um tipo anulável.
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
}Adicionando comportamentos adicionais por meio de interfaces e a matar:
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 de especial aqui. Se você tem uma função retornando Nothing :
fun quit ( status : Int ): Nothing {
exitProcess(status)
}Então você pode, por exemplo, lançar uma exceção como comportamento:
every { quit( 1 ) } throws Exception ( " this is a test " ) Uma simulação escopo é uma simulação que se desmala automaticamente depois que o bloco de código passado como um parâmetro foi executado. Você pode usar as funções mockkObject , mockkStatic e 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 ))
} Uma maneira muito simples de criar novos Matchers é anexar uma função ao MockKMatcherScope ou MockKVerificationScope e usando a função match :
fun MockKMatcherScope. seqEq ( seq : Sequence < String >) = match< Sequence < String >> {
it.toList() == seq.toList()
} Também é possível criar letos mais avançados implementando a interface Matcher .
Exemplo de um correspondente personalizado que compara a lista sem ordem:
@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))Exemplo usando a reflexão para zombar de todos os métodos em um objeto de estilo construtor
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 os parâmetros globalmente, existem algumas configurações que você pode especificar em um arquivo de recurso.
Como usar:
io/mockk/settings.properties no src/main/resources . relaxed =true|false
relaxUnitFun =true|false
recordPrivateCalls =true|false
stackTracesOnVerify =true|false
stackTracesAlignment =left|center stackTracesAlignment determina se alinhar os traços de pilha no centro (padrão) ou à esquerda (mais consistente com as pilhas de pilha JVM usuais).
Aqui estão algumas mesas para ajudá -lo a dominar o DSL.
| Função | Descrição |
|---|---|
mockk<T>(...) | Construa uma zombaria regular |
spyk<T>() | Construa um espião usando o construtor padrão |
spyk(obj) | Construa um espião copiando do obj |
slot | cria um slot de captura |
every | Inicia um bloco de stubbing |
coEvery | Inicia um bloco de stubbing para coroutines |
verify | Inicia um bloco de verificação |
coVerify | Inicia um bloco de verificação para coroutinas |
verifyAll | Inicia um bloco de verificação que deve incluir todas as chamadas |
coVerifyAll | Inicia um bloco de verificação que deve incluir todas as chamadas para Coroutines |
verifyOrder | Inicia um bloco de verificação que verifica o pedido |
coVerifyOrder | Inicia um bloco de verificação que verifica o pedido de coroutines |
verifySequence | Inicia um bloco de verificação que verifica se todas as chamadas foram feitas em uma sequência especificada |
coVerifySequence | Inicia um bloco de verificação que verifica se todas as chamadas foram feitas em uma sequência especificada para Coroutines |
excludeRecords | exclua algumas ligações de serem gravadas |
confirmVerified | confirma que todas as chamadas gravadas foram verificadas |
checkUnnecessaryStub | confirma que todas as chamadas gravadas são usadas pelo menos uma vez |
clearMocks | limpa zombares especificadas |
registerInstanceFactory | permite que você redefina o caminho de instanciação para determinado objeto |
mockkClass | constrói uma simulação regular passando a classe como parâmetro |
mockkObject | transforma um objeto em um objeto zombar ou o limpa se já foi transformado |
unmockkObject | transforma um objeto de volta em um objeto regular |
mockkStatic | faz uma zombaria estática de uma classe ou a limpa se já foi transformada |
unmockkStatic | transforma uma simulação estática de volta em uma aula regular |
clearStaticMockk | Limpa uma simulação estática |
mockkConstructor | faz com que um construtor zombe de uma classe ou a limpa se já foi transformada |
unmockkConstructor | transforma um construtor de volta em uma aula regular |
clearConstructorMockk | Limpa a zombaria do construtor |
unmockkAll | desmocas objeto, manchas estáticas e construtores |
clearAllMocks | Limpa zombares regulares, de objeto, estático e construtor |
Por padrão, argumentos simples são correspondidos usando eq()
| Matcher | Descrição |
|---|---|
any() | corresponde a qualquer argumento |
any(Class) | corresponde a qualquer argumento da aula do Give (para zombaria reflexiva) |
allAny() | Matcher especial que usa any() em vez de eq() para matcores que são fornecidos como argumentos simples |
isNull() | verifica se o valor é nulo |
isNull(inverse=true) | verifica se o valor não é nulo |
ofType(type) | verifica se o valor pertence ao tipo |
match { it.startsWith("string") } | correspondem através do predicado passado |
coMatch { it.startsWith("string") } | correspondências através do predicado de corotagem passada |
matchNullable { it?.startsWith("string") } | corresponde ao valor nulo através do predicado passado |
coMatchNullable { it?.startsWith("string") } | corresponde ao valor anulado através do predicado de corotação passada |
eq(value) | corresponde se o valor for igual ao valor fornecido através da função deepEquals |
eq(value, inverse=true) | corresponde se o valor não for igual ao valor fornecido através da função deepEquals |
neq(value) | corresponde se o valor não for igual ao valor fornecido através da função deepEquals |
refEq(value) | corresponde se o valor for igual ao valor fornecido por comparação de referência |
refEq(value, inverse=true) | corresponde se o valor não for igual ao valor fornecido via comparação de referência |
nrefEq(value) | corresponde se o valor não for igual ao valor fornecido via comparação de referência |
cmpEq(value) | corresponde se o valor for igual ao valor fornecido por meio da função compareTo |
less(value) | corresponde se o valor for menor que o valor fornecido por meio da função compareTo |
more(value) | corresponde se o valor for mais do que o valor fornecido por meio da função compareTo |
less(value, andEquals=true) | corresponde se o valor for menor ou igual ao valor fornecido por meio da função compareTo |
more(value, andEquals=true) | corresponde se o valor for maior ou igual ao valor fornecido por meio da função compareTo |
range(from, to, fromInclusive=true, toInclusive=true) | corresponde se o valor estiver em intervalo através da função compareTo |
and(left, right) | combina dois fósforos por meio de um lógico e |
or(left, right) | combina dois fósforos por meio de um lógico ou |
not(matcher) | nega o Matcher |
capture(slot) | captura um valor não anulável para um CapturingSlot |
captureNullable(slot) | captura um valor anulável para um CapturingSlot |
capture(mutableList) | captura um valor para uma lista |
captureNullable(mutableList) | captura um valor para uma lista juntamente com valores nulos |
captureLambda() | captura um lambda |
captureCoroutine() | captura uma coroutina |
invoke(...) | chama um argumento correspondente |
coInvoke(...) | chama um argumento correspondente para uma coroutina |
hint(cls) | sugere o próximo tipo de retorno, caso seja apagado |
anyVararg() | corresponde a todos os elementos em uma vararja |
varargAny(matcher) | corresponde se algum elemento corresponde ao Matcher |
varargAll(matcher) | corresponde se todos os elementos correspondem ao Matcher |
any...Vararg() | corresponde a todos os elementos em vararg (específico para o tipo primitivo) |
varargAny...(matcher) | corresponde se algum elemento corresponde ao Matcher (específico do tipo primitivo) |
varargAll...(matcher) | corresponde se todos os elementos correspondem ao Matcher (específico do tipo primitivo) |
Alguns Matchers especiais disponíveis apenas no modo de verificação:
| Matcher | Descrição |
|---|---|
withArg { code } | corresponde a qualquer valor e permite executar algum código |
withNullableArg { code } | corresponde a qualquer valor anulado e permite executar algum código |
coWithArg { code } | corresponde a qualquer valor e permite executar algum código de coroutina |
coWithNullableArg { code } | corresponde a qualquer valor anulável e permite executar algum código de coroutina |
| Validador | Descrição |
|---|---|
verify { mock.call() } | Verificação não ordenada de que uma chamada foi realizada |
verify(inverse=true) { mock.call() } | Verificação não ordenada de que uma chamada não foi realizada |
verify(atLeast=n) { mock.call() } | Verificação não ordenada de que uma chamada foi realizada pelo menos n vezes |
verify(atMost=n) { mock.call() } | Verificação não ordenada de que uma chamada foi realizada na maioria n vezes |
verify(exactly=n) { mock.call() } | Verificação não ordenada de que uma chamada foi realizada exatamente n vezes |
verifyAll { mock.call1(); mock.call2() } | Verificação não ordenada de que apenas as chamadas especificadas foram executadas para as manchas mencionadas |
verifyOrder { mock.call1(); mock.call2() } | Verifique se a sequência de chamadas foi uma após a outra |
verifySequence { mock.call1(); mock.call2() } | Verificação de que apenas a sequência especificada de chamadas foi executada para as zombas mencionadas |
verify { mock wasNot Called } | Verificação de que uma simulação não foi chamada |
verify { listOf(mock1, mock2) wasNot Called } | Verificação de que uma lista de zombares não foi chamada |
Uma resposta pode ser seguida por uma ou mais respostas adicionais.
| Responder | Descrição |
|---|---|
returns value | Especifique que a chamada correspondente retorna um valor especificado |
returnsMany list | Especifique que a chamada correspondente retorna um valor da lista, com chamadas subsequentes retornando o próximo elemento |
returnsArgument(n) | especifique que a chamada correspondente retorna o enésimo argumento dessa chamada |
throws ex | especifique que a chamada correspondente lança uma exceção |
throwsMany list | Especifique que a chamada correspondente lança uma exceção da lista, com chamadas subsequentes jogando a próxima exceção |
answers { code } | Especifique que as respostas de chamada correspondida com um bloco de código escopo com answer scope |
coAnswers { code } | Especifique que as respostas de chamada correspondida com um bloco de código de coroutina com answer scope |
answers answerObj | Especifique que as respostas de chamada correspondida com um objeto de resposta |
answers { nothing } | Especifique que as respostas de chamada correspondida NULL |
just Runs | Especifique que a chamada correspondente está retornando (retorna nulo) |
just Awaits | Especifique que a chamada correspondente nunca retorna (disponível desde v1.13.3) |
propertyType Class | Especifique o tipo de acessador de campo de apoio |
nullablePropertyType Class | Especifique o tipo de acessador de campo de apoio como tipo Nullable |
Uma próxima resposta é devolvida em cada chamada consequente e o último valor é persistido. Portanto, isso é semelhante à semântica returnsMany .
| Resposta adicional | Descrição |
|---|---|
andThen value | Especifique que a chamada correspondente retorna um valor especificado |
andThenMany list | Especifique que a chamada correspondente retorna um valor da lista, com chamadas subsequentes retornando o próximo elemento |
andThenThrows ex | especifique que a chamada correspondente lança uma exceção |
andThenThrowsMany ex | Especifique que a chamada correspondente lança uma exceção da lista, com chamadas subsequentes jogando a próxima exceção |
andThen { code } | Especifique que as respostas de chamada correspondida com um bloco de código escopo com answer scope |
coAndThen { code } | Especifique que as respostas de chamada correspondida com um bloco de código de coroutina com answer scope |
andThenAnswer answerObj | Especifique que as respostas de chamada correspondida com um objeto de resposta |
andThen { nothing } | Especifique que as respostas de chamada correspondida NULL |
andThenJust Runs | Especifique que a chamada correspondente está retornando (disponível desde v1.12.2) |
andThenJust Awaits | Especifique que a chamada correspondente nunca está retornando (disponível desde v1.13.3) |
| Parâmetro | Descrição |
|---|---|
call | um objeto de chamada que consiste em uma invocação e um latário |
invocation | contém informações sobre a função real invocada |
matcher | contém informações sobre o Matcher usado para corresponder à invocação |
self | referência à invocação de objetos feita |
method | referência à invocação de funções feita |
args | referência aos argumentos de invocação |
nArgs | Número de argumentos de invocação |
arg(n) | nº argumento |
firstArg() | primeiro argumento |
secondArg() | segundo argumento |
thirdArg() | terceiro argumento |
lastArg() | Último argumento |
captured() | O último elemento da lista por conveniência ao capturar para uma lista |
lambda<...>().invoke() | Chame o lambda capturado |
coroutine<...>().coInvoke() | Chame a coroutina capturada |
nothing | valor nulo para retornar nothing como resposta |
fieldValue | acessador ao campo de apoio à propriedade |
fieldValueAny | acessador ao campo de apoio da propriedade com Any? tipo |
value | valor que está sendo definido, fundido para o mesmo tipo do campo de apoio da propriedade |
valueAny | valor sendo definido, com Any? tipo |
callOriginal | chama a função original |
| Parâmetro | Descrição |
|---|---|
position | a posição de um argumento em uma matriz de vararja |
nArgs | contagem geral de argumentos em uma matriz de vararg |
Você também pode apoiar este projeto, tornando -se um patrocinador. Seu logotipo aparecerá aqui com um link para o seu site.
Obrigado a todos os nossos apoiadores!
Este projeto existe graças a todas as pessoas que contribuem.
Para fazer perguntas, use o excesso de pilha ou o Gitter.
Para relatar bugs, use o projeto GitHub.