Slide 1

Slide 1 text

mocking libaray for Kotlin Joe Tsai 2018.09.20 @ Android Taipei

Slide 2

Slide 2 text

Joe Tsai Android Developer https://medium.com/joe-tsai

Slide 3

Slide 3 text

Kotlin + Mockito 可能會遇到的問題

Slide 4

Slide 4 text

嘗試 @mock Class org.mockito.exceptions.base.MockitoException: Cannot mock/spy class com.joetsai.kotlinunittest.token.TokenRepository Mockito cannot mock/spy because : - final class

Slide 5

Slide 5 text

嘗試 @mock Class org.mockito.exceptions.base.MockitoException: Cannot mock/spy class com.joetsai.kotlinunittest.token.TokenRepository Mockito cannot mock/spy because : - final class → 在 Kotlin 中任何 Class 預設為 final 解法 1. Open class 2. 開啟隱藏版 mock final 功能

Slide 6

Slide 6 text

其他可能遇到的問題 ● java.lang.IllegalStateException: anyObject() must not be null any(), eq(), capture(), captor() ● `when` 解法 1. MockitoKotlinHelpers.kt 2. Mockito-Kotlin

Slide 7

Slide 7 text

測試 static 方法(PowerMock) ● 初始化繁瑣,使用上不直覺 ● “Mockito2 support is currently experimental. A lot of issues are not resolved still.” 版本兼容性

Slide 8

Slide 8 text

只是想用 Kotlin 寫單元測試而已 有必要這麼折磨人嗎?

Slide 9

Slide 9 text

如果不用處理上述問題又可以測試靜態方法, 該有多好?

Slide 10

Slide 10 text

無痛在 Kotlin 下使用 Mockito + PowerMock

Slide 11

Slide 11 text

Gradle Setting dependencies { testImplementation 'io.mockk:mockk:1.8.6' ... }

Slide 12

Slide 12 text

Example: 小孩要零用錢 class Kid(private val mother: Mother) { var money = 0 private set fun wantMoney() { money += mother.giveMoney() } } class Mother { fun giveMoney(): Int { return 100 } }

Slide 13

Slide 13 text

mockk, every class KidTest { @Test fun wantMoney() { // Given val mother = mockk() val kid = Kid(mother) every { mother.giveMoney() } returns 30 // when().thenReturn() in Mockito // When kid.wantMoney() // Then assertEquals(30, kid.money) } }

Slide 14

Slide 14 text

Annotation class KidAnnotationTest { @MockK lateinit var mother: Mother lateinit var kid: Kid @Before fun setUp() { MockKAnnotations.init(this) kid = Kid(mother) } @Test fun wantMoney() { every { mother.giveMoney() } returns 30 kid.wantMoney() assertEquals(30, kid.money) } }

Slide 15

Slide 15 text

需求變更:通知媽媽 class Kid(private val mother: Mother) { var money = 0 private set fun wantMoney() { mother.inform(money) money += mother.giveMoney() } } class Mother { fun inform(money: Int) { println("媽媽我現在有 $money 元,我要跟你拿錢!") } fun giveMoney(): Int { return 100 } }

Slide 16

Slide 16 text

Verify @Test fun wantMoney() { // When val mother = mockk() val kid = Kid(mother) every { mother.giveMoney() } returns 30 // Given kid.wantMoney() // Then verify { mother.inform(any()) } assertEquals(30, kid.money) }

Slide 17

Slide 17 text

Verify @Test fun wantMoney() { // When val mother = mockk() val kid = Kid(mother) every { mother.giveMoney() } returns 30 // Given kid.wantMoney() // Then verify { mother.inform(any()) } assertEquals(30, kid.money) } io.mockk.MockKException: no answer found for: Mother(#1).inform(0)

Slide 18

Slide 18 text

Verify @Test fun wantMoney() { // When val mother = mockk() val kid = Kid(mother) every { mother.giveMoney() } returns 30 // Given kid.wantMoney() // Then verify { mother.inform(any()) } assertEquals(30, kid.money) } io.mockk.MockKException: no answer found for: Mother(#1).inform(0) 在 MockK 預設的 mock 是很嚴謹的,必須指定所 有的行為操作。

Slide 19

Slide 19 text

Verify @Test fun wantMoney() { // When val mother = mockk() val kid = Kid(mother) every { mother.giveMoney() } returns 30 // Given kid.wantMoney() // Then verify { mother.inform(any()) } assertEquals(30, kid.money) } io.mockk.MockKException: no answer found for: Mother(#1).inform(0) 在 MockK 預設的 mock 是很嚴謹的,必須指定所 有的行為操作。 every { mother.inform(any()) } just Runs

Slide 20

Slide 20 text

Relaxed Mocks 該物件所有的方法都不需要指定 // 第一種 val mother = mockk(relaxed = true) // 第二種 @RelaxedMockK lateinit var mother: Mother

Slide 21

Slide 21 text

Relaxed Unit Function 不需指定無回傳值的方法 @MockK lateinit var mother: Mother @Before fun setUp() { MockKAnnotations.init(this, relaxUnitFun = true) } @MockK(relaxUnitFun = true) lateinit var mother: Mother

Slide 22

Slide 22 text

Verify verify { mother.inform(any()) } verify(exactly = 0) { mother.inform(any()) } verify { mother.inform(any()) mother.giveMoney() } // inform() 的下一個執行的方法一定要是 giveMoney() verifySequence { mother.inform(any()) mother.giveMoney() } // inform() 只要在 giveMoney() 之前執行即可 verifyOrder { mother.inform(any()) mother.giveMoney() }

Slide 23

Slide 23 text

Capture 用途:抓取方法參數 種類:slot, mutableListOf class Mother { fun inform(money: Int) { println("媽媽我現在有 $money 元,我要跟你拿錢!") } ... } val slot = slot() every { mother.inform(capture(slot)) } just Runs kid.wantMoney() assertEquals(0, slot.captured)

Slide 24

Slide 24 text

Static Method class Util { fun ok() { UtilJava.ok() UtilKotlin.ok() } } object UtilKotlin { @JvmStatic fun ok(): String { return "UtilKotlin.ok()" } } public class UtilJava { static String ok() { return "UtilJava.ok()"; } }

Slide 25

Slide 25 text

@Test fun ok() { // Given val util = Util() mockkStatic(UtilJava::class) mockkStatic(UtilKotlin::class) every { UtilJava.ok() } returns "Joe" every { UtilKotlin.ok() } returns "Tsai" // When util.ok() // Then verify { UtilJava.ok() } verify { UtilKotlin.ok() } assertEquals("Joe", UtilJava.ok()) assertEquals("Tsai", UtilKotlin.ok()) } MockkStatic

Slide 26

Slide 26 text

Companion Object ? class Util { fun ok() { UtilKotlin.ok() } } class UtilKotlin { companion object { @JvmStatic fun ok(): String { return "UtilKotlin.ok()" } } }

Slide 27

Slide 27 text

Test Companion Object @Test fun ok() { // Given val util = Util() mockkObject(UtilKotlin) mockkObject(UtilKotlin.Companion) every { UtilKotlin.ok() } returns "Tsai" // When util.ok() // Then verify { UtilKotlin.ok() } assertEquals("Tsai", UtilKotlin.ok()) } 兩個都能正常運作

Slide 28

Slide 28 text

class Util { fun ok() { UtilKotlin.ok() } } Singleton object UtilKotlin { @JvmStatic fun ok(): String { return "UtilKotlin.ok()" } }

Slide 29

Slide 29 text

MockkObject @Test fun ok() { // Given val util = Util() mockkObject(UtilKotlin) every { UtilKotlin.ok() } returns "Tsai" // When util.ok() // Then verify { UtilKotlin.ok() } assertEquals("Tsai", UtilKotlin.ok()) }

Slide 30

Slide 30 text

What’s More ● Spy ● Extension functions ● Private functions ● Coroutines

Slide 31

Slide 31 text

Reference ● MockK.io ● MOCKING IN KOTLIN WITH MOCKK ● Kotlin: Static Methods

Slide 32

Slide 32 text

Let’s mockK