Mockk详解

简述

  mockk和mockito类似,都是在测试环境下制造test double的测试框架

  在kotlin环境下mockk比mockito更加优秀

mockito在kotlin的缺陷

  mockito的when方法在kotlin是关键字,需要加反引号才能使用

  在kotlin里所有class都是final,而mockito不能mock final的class

  mockito不支持静态方法

引入依赖

  https://mvnrepository.com/artifact/io.mockk/mockk

gradle

testImplementation 'io.mockk:mockk:1.13.3'

maven 

<dependency>
    <groupId>io.mockk</groupId>
    <artifactId>mockk</artifactId>
    <version>1.13.3</version>
    <scope>test</scope>
</dependency>

demo工程 

基本用法

  这里我们以Kid和Mother类举例

class Kid(private val mother: Mother) {
    private var money = 0
    fun wantMoney() {
        money += mother.giveMoney()
    }
}
class Mother {
    fun giveMoney(): Int {
        return 300;
    }
}

制作替身

  使用mockk方法,填入对应替身的泛型,即可制作该Class的替身

mockk<Class>()

  这里制作Mother类的替身

val mother = mockk<Mother>()

打桩

  使用every方法对替身进行打桩,和mockito的when.thenReturn类似,即指定该替身的方法返回值

  我们这里对giveMoney进行打桩,令其返回100

every { mother.giveMoney() } returns 100

  最简单的mockk任务到这里就结束了,接下来就可以进行测试方法和结果断言,完整测试方法如下:

@Test
fun shouldReturn30WhenMotherGiveMoneyIs30() {
    //given
    val mother = mockk<Mother>()
    every { mother.giveMoney() } returns 100
    val kid = Kid(mother)
    //when
    kid.wantMoney()
    //then
    assertEquals(100,kid.money)
}

注解

  和mockito类似,mockk也支持用注解进行初始化

  在成员变量上使用@Mockk注解,代表带回会自动创建该类的替身,然后在BeforeTest的配置中使用

MockKAnnotations.init(this),即可初始化替身

  使用@InjectMockKs注解可初始化测试替身作为构造函数参数的对象

import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.InjectMockKs
import io.mockk.impl.annotations.MockK
import io.mockk.mockk
import org.junit.jupiter.api.Test
import kotlin.test.BeforeTest
import kotlin.test.assertEquals

class KidTest {

    @MockK
    lateinit var mother: Mother

    @InjectMockKs
    lateinit var kid: Kid

    @BeforeTest
    fun setUp() {
        MockKAnnotations.init(this)
    }

    @Test
    fun shouldReturn30WhenMotherGiveMoneyIs30() {
        //given
        every { mother.giveMoney() } returns 100
        //when
        kid.wantMoney()
        //then
        assertEquals(100, kid.money)
    }
}

verify

  和mockito类似,我们可以使用verify来对替身的方法进行判断,看它有没有被调用过

verify { mother.giveMoney() }

  我们还可以verify该方法是否被调用了一定次数,在verify前加上exactly配置即可

verify(exactly = 0) { mother.giveMoney() }

  如果想验证方法被调用是否满足一定顺序,则可以使用verifySequence 和 verifyOrder

verifySequence {
    mother.inform(any())
    mother.giveMoney()
}
verifyOrder {
    mother.inform(any())
    mother.giveMoney()
}

  前者表示方法的执行一定要按这个顺序一条条执行下去,后者表示中间可以加入其他方法的调用,整体顺序是这样就行

当没有对所有方法打桩时

  现在Mother类有两个方法

class Mother {
    fun giveMoney(): Int {
        return 300;
    }
    fun doSomeThing() {
        println("do some thing")
    }
}

  kid的方法变成这样

class Kid(private val mother: Mother) {
    var money = 0
    fun wantMoney() {
        money += mother.giveMoney()
        mother.doSomeThing()
    }
}

  我们如果只对一个方法进行打桩

class KidTest {

    @MockK
    lateinit var mother: Mother

    @InjectMockKs
    lateinit var kid: Kid

    @BeforeTest
    fun setUp() {
        MockKAnnotations.init(this)
    }

    @Test
    fun shouldReturn30WhenMotherGiveMoneyIs30() {
        //given
        every { mother.giveMoney() } returns 100
        //when
        kid.wantMoney()
        //then
        assertEquals(100, kid.money)
        verify { mother.giveMoney() }
        verify { mother.doSomeThing() }
    }
}

  就会报错

  那是因为mockk要求每一个被调用过的替身对象的方法都要打桩

  那问题来了,如果有100个方法,那要打100次桩吗

relaxed

  在进行制作测试替身时,可以指定relaxed = true,意思是该替身所有方法都不用打桩

val mother = mockk<Mother>(relaxed = true)

  也可以使用注解的方式@RelaxedMockK

@RelaxedMockK
lateinit var mother: Mother
  RelaxUnitFun则是无需为无返回值的方法打桩,仅对有返回值的方法打桩就行

  同样有配置和注解两种方法

val mocker = mockk<Mother>(relaxUnitFun = true)
@MockK(relaxUnitFun = true)
lateinit var mother: Mother

对静态方法mock

  我们使用object对象+@JvmStatic注解的方式进行编写kotlin静态方法

object KotlinUtil {
    @JvmStatic
    fun ok(): String {
        return "UtilKotlin.ok()"
    }
}

  然后创建一个方法去使用这个静态方法

class KotlinUtilUsage {
    fun useKotlinUtil() {
        KotlinUtil.staticDoSomething()
    }
}

  测试的时候,可以直接使用mockkStatic进行mock静态测试,直接使用every进行打桩

class StaticMethodTest {

    @Test
    fun staticMethodTest() {
        //given
        val kotlinUtilUsage = KotlinUtilUsage()
        mockkStatic(KotlinUtil::class)
        every { KotlinUtil.staticDoSomething() } returns "mocked returns"
        //when
        kotlinUtilUsage.useKotlinUtil()
        //then
        verify { KotlinUtil.staticDoSomething() }
    }
}

References

https://blog.csdn.net/ljt2724960661/article/details/118251823

https://medium.com/joe-tsai/mockk-%E4%B8%80%E6%AC%BE%E5%BC%B7%E5%A4%A7%E7%9A%84-kotlin-mocking-library-part-2-4-4be059331110

https://juejin.cn/post/6877824384694747143

https://www.jianshu.com/p/8e392a08d99d

 

 

 

 

 

 

 

 

 

 

 

posted @ 2023-03-14 17:35  艾尔夏尔-Layton  阅读(659)  评论(0编辑  收藏  举报