• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

mockk/mockk: mocking library for Kotlin

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称(OpenSource Name):

mockk/mockk

开源软件地址(OpenSource Url):

https://github.com/mockk/mockk

开源编程语言(OpenSource Language):

Kotlin 84.8%

开源软件介绍(OpenSource Introduction):

mockk kotlin

Gitter Relase Version Change log codecov Android Matrix tests Open Source Helpers

Kotlin Academy articles

Check the series of articles "Mocking is not rocket science" at Kt. Academy describing MockK from the very basics of mocking up to description of all advanced features.

Spring support

Quarkus support

  • quarkus-mockk adds support for mocking beans in Quarkus. Documentation can be found here

Kotlin version support

From version 1.13.0 MockK supports Kotlin 1.4 and higher

Known issues

  • PowerMock needs a workaround to run together with MockK #79. (not sure after workaround if it is generally usable or not, please somebody report it)
  • Inline functions cannot be mocked: see the discussion on this issue

Table of contents:

  • auto-gen TOC: {:toc}

Examples, guides & articles

Japanese guides and articles

Chinese guides and articles

Installation

All you need to get started is just to add a dependency to MockK library.

Gradle/maven dependency

ApproachInstruction
Gradle    
testImplementation "io.mockk:mockk:{version}"
Gradle (Kotlin DSL)    
testImplementation("io.mockk:mockk:{version}")
Maven
<dependency>
    <groupId>io.mockk</groupId>
    <artifactId>mockk</artifactId>
    <version>{version}</version>
    <scope>test</scope>
</dependency>
android Unit
testImplementation "io.mockk:mockk:{version}"
testImplementation "io.mockk:mockk-agent-jvm:{version}"
android Instrumented
androidTestImplementation "io.mockk:mockk-android:{version}"
androidTestImplementation "io.mockk:mockk-agent-jvm:{version}"
Common multiplatform    
testImplementation "io.mockk:mockk-common:{version}"

DSL examples

Simplest example. By default mocks are strict, so you need to provide some behaviour.

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)

Annotations

You can use annotations to simplify the creation of mock objects:

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
  }
}

Injection first tries to match properties by name, then by class or superclass. Check the lookupType parameter for customization.

Properties are injected even if private is applied. Constructors for injection are selected from the biggest number of arguments to lowest.

@InjectMockKs by default injects only lateinit vars or vars that are not assigned. To change this, use overrideValues = true. This would assign the value even if it is already initialized somehow. To inject vals, use injectImmutable = true. For a shorter notation use @OverrideMockKs which does the same as @InjectMockKs by default, but turns these two flags on.

JUnit4

JUnit 4 exposes a rule-based API to allow for some automation following the test lifecycle. MockK includes a rule which uses this to set up and tear down your mocks without needing to manually call MockKAnnotations.init(this). Example:

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

In JUnit5 you can use MockKExtension to initialize your mocks.

@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
  }
}

Additionally, it adds the possibility to use @MockK and @RelaxedMockK on test function parameters:

@Test
fun calculateAddsValues1(@MockK car1: Car, @RelaxedMockK car2: Car) {
  // ... use car1 and car2
}

Finally, this extension will call unmockkAll in a @AfterAll callback, ensuring your test environment is clean after each test class execution. You can disable this behavior by adding the @MockKExtension.KeepMocks annotation to your class or globally by setting the mockk.junit.extension.keepmocks=true property

Automatic verification confirmation

You can make sure that all stubbed methods are actually verified by also annotating your test class with @MockKExtension.ConfirmVerification.

This will internally call confirmVerified on all mocks after each test, to make sure there are no unnecessary stubbings.

Please note that this behavior may not work as expected when running tests in your IDE, as it is Gradle who takes care of handling the exception being thrown when these confirmVerified calls fail.

Automatic unnecessary stubbing check

You can make sure that all stubbed methods are useful - used at least once - by also annotating your test class with @MockKExtension.CheckUnnecessaryStub.

This will internally call checkUnnecessaryStub on all mocks after each test, to make sure there are no unnecessary stubbings.

Spy

Spies allow you to mix mocks and real objects.

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)

Note: the spy object is a copy of the passed object.

Relaxed mock

A relaxed mock is the mock that returns some simple value for all functions. This allows you to skip specifying behavior for each case, while still stubbing things you need. For reference types, chained mocks are returned.

val car = mockk<Car>(relaxed = true)

car.drive(Direction.NORTH) // returns null

verify { car.drive(Direction.NORTH) }

confirmVerified(car)

Note: relaxed mocking is working badly with generic return types. A class cast exception is usually thrown in this case. Opt for stubbing manually in the case of a generic return type.

Workaround:

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()

Partial mocking

Sometimes, you need to stub some functions, but still call the real method on others, or on specific arguments. This is possible by passing callOriginal() to answers, which works for both relaxed and non-relaxed mocks.

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

Mock relaxed for functions returning Unit

If you want Unit-returning functions to be relaxed, you can use relaxUnitFun = true as an argument to the mockk function, @MockKannotation or MockKAnnotations.init function.

Function:

mockk<ClassBeingMocked>(relaxUnitFun = true)

Annotation:

@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 mocks

Objects can be turned into mocks in the following way:

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))

To revert back, use unmockkAll or unmockkObject:

@Before
fun beforeTests() {
    mockkObject(ObjBeingMocked)
    every { MockObj.add(1,2) } returns 55
}

@Test
fun willUseMockBehaviour() {
    assertEquals(55, ObjBeingMocked.add(1,2))
}

@After
fun afterTests() {
    unmockkAll()
    // or unmockkObject(ObjBeingMocked)
}

Despite the Kotlin language restrictions, you can create new instances of objects if required by testing logic:

val newObjectMock = mockk<MockObj>()

Class mock

Sometimes you need a mock of an arbitrary class. Use mockkClass in those cases.

val car = mockkClass(Car::class)

every { car.drive(Direction.NORTH) } returns Outcome.OK

car.drive(Direction.NORTH) // returns OK

verify { car.drive(Direction.NORTH) }

Enumeration mocks

Enums can be mocked using 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)

Constructor mocks

Sometimes, especially in code you don't own, you need to mock newly created objects. For this purpose, the following constructs are provided:

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) }

The basic idea is that just after the constructor of the mocked class is executed (any of them), objects become a constructed mock.
Mocking behavior of such a mock is connected to the special prototype mock denoted by anyConstructed<MockCls>().
There is one instance per class of such a prototype mock. Call recording also happens to the prototype mock.
If no behavior for the function is specified, then the original function is executed.

In case a class has more than one constructor, each can be mocked separately:

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)
}

Note that in this case, a prototype mock is created for every set of argument matchers passed to constructedWith.

Partial argument matching

You can mix both regular arguments and 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)

Chained calls

You can stub chains of calls:

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)

Note: if the function's return type is generic then the information about the actual type is gone.
To make chained calls work, additional information is required.
Most of the time the framework will catch the cast exception and do autohinting.
In the case it is explicitly required, use hint before making the next call.

every { obj.op2(1, 2).hint(Int::class).op1(3, 4) } returns 5

Hierarchical mocking

From version 1.9.1 mocks may be chained into hierarchies:

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"
            }
        }
    )
}


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
zotero/web-library发布时间:2022-08-15
下一篇:
schteppe/p2.js: JavaScript 2D physics library发布时间:2022-08-15
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap