$30 off During Our Annual Pro Sale. View Details »

Mocking Kotlin with MockK

Hadi Tok
September 20, 2019

Mocking Kotlin with MockK

Introduction to the mocking library MockK

Hadi Tok

September 20, 2019
Tweet

More Decks by Hadi Tok

Other Decks in Programming

Transcript

  1. Mocking Kotlin With MockK
    Hadi Tok
    CitizenMe
    @hadi_tok

    View Slide

  2. Unit Testing

    View Slide

  3. Unit Testing
    ● Unit: Smallest testable component (function, class)
    ● Units are tested independently from each other
    ● Other units with dependencies are mocked or faked

    View Slide

  4. Unit Testing

    View Slide

  5. Testable Code
    ● Separations of Concerns
    ● Dependency injection
    ● Clean Code

    View Slide

  6. Stub
    ● Returning values without logic.
    ● Could return constant values or use a lookup table

    View Slide

  7. Fake
    ● Could have lightweight logic
    ● Cut corners for testing purposes so can’t be used in
    production

    View Slide

  8. Mock
    Northern mockingbird (Mimus polyglottos)

    View Slide

  9. Mock
    ● Not authentic or real, but without the intention to
    deceive.
    ● Imitates objects to specify behaviour explicitly and
    enables full control over the object.
    ● Usually backed with a library/framework

    View Slide

  10. Mocking
    ● What functions and variables return
    ● Check if the function calls and variable assignments are
    made with correct values

    View Slide

  11. MockK

    View Slide

  12. Why MockK
    ● Pure Kotlin
    ● Easy to use Domain Specific Language(DSL)
    ● Can also mock Java

    View Slide

  13. Sample

    View Slide

  14. data class User(val userId: String, val userName: String)

    View Slide

  15. data class User(val userId: String, val userName: String)
    interface UserApi {
    fun deleteUser(userId: String): Boolean
    }

    View Slide

  16. data class User(val userId: String, val userName: String)
    interface UserApi {
    fun deleteUser(userId: String): Boolean
    }
    class UserManager(private val userApi: UserApi) {
    fun deleteUser(user: User): Boolean {
    return userApi.deleteUser(user.userId)
    }
    }

    View Slide

  17. @Test
    fun basicTest() {
    val userApi: UserApi = mockk()
    }

    View Slide

  18. @Test
    fun basicTest() {
    val userApi: UserApi = mockk()
    val userManager = UserManager(userApi)
    }

    View Slide

  19. @Test
    fun basicTest() {
    val userApi: UserApi = mockk()
    val userManager = UserManager(userApi)
    val userId = "123"
    val user: User = mockk()
    every { user.userId } returns userId
    }

    View Slide

  20. @Test
    fun basicTest() {
    val userApi: UserApi = mockk()
    val userManager = UserManager(userApi)
    val userId = "123"
    val user: User = mockk()
    every { user.userId } returns userId
    every { userApi.deleteUser(userId) } returns true
    }

    View Slide

  21. @Test
    fun basicTest() {
    val userApi: UserApi = mockk()
    val userManager = UserManager(userApi)
    val userId = "123"
    val user: User = mockk()
    every { user.userId } returns userId
    every { userApi.deleteUser(userId) } returns true
    val result = userManager.deleteUser(user)
    }

    View Slide

  22. @Test
    fun basicTest() {
    val userApi: UserApi = mockk()
    val userManager = UserManager(userApi)
    val userId = "123"
    val user: User = mockk()
    every { user.userId } returns userId
    every { userApi.deleteUser(userId) } returns true
    val result = userManager.deleteUser(user)
    assert(result).toBe(true)
    }

    View Slide

  23. @Test
    fun basicTest() {
    val userApi: UserApi = mockk()
    val userManager = UserManager(userApi)
    val userId = "123"
    val user: User = mockk()
    every { user.userId } returns userId
    every { userApi.deleteUser(userId) } returns true
    val result = userManager.deleteUser(user)
    assert(result).toBe(true)
    verify { userApi.deleteUser(userId) }
    }

    View Slide

  24. Argument Matching

    View Slide

  25. @Test
    fun basicTest() {
    val userApi: UserApi = mockk()
    val userManager = UserManager(userApi)
    val userId = "123"
    val user: User = mockk()
    every { user.userId } returns userId
    every { userApi.deleteUser(userId) } returns true
    val result = userManager.deleteUser(user)
    assert(result).toBe(true)
    verify { userApi.deleteUser(userId) }
    }

    View Slide

  26. @Test
    fun basicTest() {
    val userApi: UserApi = mockk()
    val userManager = UserManager(userApi)
    val userId = "123"
    val user: User = mockk()
    every { user.userId } returns userId
    every { userApi.deleteUser(userId) } returns true
    val result = userManager.deleteUser(user)
    assert(result).toBe(true)
    verify { userApi.deleteUser(match { it.startsWith("12") }) }
    }

    View Slide

  27. @Test
    fun basicTest() {
    val userApi: UserApi = mockk()
    val userManager = UserManager(userApi)
    val userId = "123"
    val user: User = mockk()
    every { user.userId } returns userId
    every { userApi.deleteUser(userId) } returns true
    val result = userManager.deleteUser(user)
    assert(result).toBe(true)
    verify { userApi.deleteUser(any()) }
    }

    View Slide

  28. Matchers
    ● more(), less(), range()
    ● eq(), neq(), refEq(), cmpEq()
    ● isNull(), isNull(inverse=true)
    ● match{}, matchNullable{}

    View Slide

  29. Annotations

    View Slide

  30. @MockK
    lateinit var userApi: UserApi
    @MockK
    lateinit var user: User
    val userId = “123"
    lateinit var userManager:UserManager

    View Slide

  31. @MockK
    lateinit var userApi: UserApi
    @MockK
    lateinit var user: User
    val userId = “123"
    lateinit var userManager:UserManager
    @Before
    fun initTests() {
    MockKAnnotations.init(this)
    userManager = UserManager(userApi)
    every { user.userId } returns userId
    }

    View Slide

  32. @Test
    fun basicTest() {
    every { userApi.deleteUser(userId) } returns true
    val result = userManager.deleteUser(user)
    assert(result).toBe(true)
    verify { userApi.deleteUser(userId) }
    }

    View Slide

  33. Object Mock

    View Slide

  34. object Calculator {
    fun add(first: Int, second: Int) = first + second
    }

    View Slide

  35. object Calculator {
    fun add(first: Int, second: Int) = first + second
    }
    @Test
    fun objectMock() {
    val result = Calculator.add(1, 2)
    assert(result).toBe(3)
    }

    View Slide

  36. object Calculator {
    fun add(first: Int, second: Int) = first + second
    }
    @Test
    fun objectMock() {
    val result = Calculator.add(1, 2)
    assert(result).toBe(3)
    mockkObject(Calculator)
    every { Calculator.add(1, 2) } returns 42
    }

    View Slide

  37. object Calculator {
    fun add(first: Int, second: Int) = first + second
    }
    @Test
    fun objectMock() {
    val result = Calculator.add(1, 2)
    assert(result).toBe(3)
    mockkObject(Calculator)
    every { Calculator.add(1, 2) } returns 42
    }

    View Slide

  38. object Calculator {
    fun add(first: Int, second: Int) = first + second
    }
    @Test
    fun objectMock() {
    val result = Calculator.add(1, 2)
    assert(result).toBe(3)
    mockkObject(Calculator)
    every { Calculator.add(1, 2) } returns 41
    val mockedResult = Calculator.add(1, 2)
    assert(mockedResult).toBe(42)
    }

    View Slide

  39. Static Mock

    View Slide

  40. public class CalculatorStatic {
    static int add(int first, int second){
    return first+second;
    }
    }

    View Slide

  41. @Test
    fun staticMock() {
    val result = CalculatorStatic.add(1, 2)
    assert(result).toBe(3)
    }

    View Slide

  42. @Test
    fun staticMock() {
    val result = CalculatorStatic.add(1, 2)
    assert(result).toBe(3)
    mockkStatic(CalculatorStatic::class)
    }

    View Slide

  43. @Test
    fun staticMock() {
    val result = CalculatorStatic.add(1, 2)
    assert(result).toBe(3)
    mockkStatic(CalculatorStatic::class)
    every { CalculatorStatic.add(1, 2) } returns 42
    }

    View Slide

  44. @Test
    fun staticMock() {
    val result = CalculatorStatic.add(1, 2)
    assert(result).toBe(3)
    mockkStatic(CalculatorStatic::class)
    every { CalculatorStatic.add(1, 2) } returns 42
    val mockedResult = CalculatorStatic.add(1, 2)
    }

    View Slide

  45. @Test
    fun staticMock() {
    val result = CalculatorStatic.add(1, 2)
    assert(result).toBe(3)
    mockkStatic(CalculatorStatic::class)
    every { CalculatorStatic.add(1, 2) } returns 42
    val mockedResult = CalculatorStatic.add(1, 2)
    assert(mockedResult).toBe(42)
    }

    View Slide

  46. Argument Capturing

    View Slide

  47. class Calculator {
    fun add(first: Int, second: Int) = first + second
    }

    View Slide

  48. class Calculator {
    fun add(first: Int, second: Int) = first + second
    }
    @Test
    fun argumentCapturing() {
    val calculator:Calculator = mockk()
    }

    View Slide

  49. class Calculator {
    fun add(first: Int, second: Int) = first + second
    }
    @Test
    fun argumentCapturing() {
    val calculator:Calculator = mockk()
    every { calculator.add(any(), any()) } answers {
    firstArg() + secondArg()
    }
    }

    View Slide

  50. class Calculator {
    fun add(first: Int, second: Int) = first + second
    }
    @Test
    fun argumentCapturing() {
    val calculator:Calculator = mockk()
    every { calculator.add(any(), any()) } answers {
    firstArg() + secondArg()
    }
    val mockedResult = calculator.add(1, 2)
    assert(mockedResult).toBe(3)
    }

    View Slide

  51. Coroutines

    View Slide

  52. data class User(val userId: String, val userName: String)

    View Slide

  53. data class User(val userId: String, val userName: String)
    interface UserApi {
    suspend fun deleteUser(userId: String): Boolean
    }

    View Slide

  54. data class User(val userId: String, val userName: String)
    interface UserApi {
    suspend fun deleteUser(userId: String): Boolean
    }
    class UserManager(private val userApi: UserApi) {
    suspend fun deleteUser(user: User): Boolean {
    return userApi.deleteUser(user.userId)
    }
    }

    View Slide

  55. @MockK
    lateinit var userApi: UserApi
    @MockK
    lateinit var user: User
    val userId = “123"
    lateinit var userManager:UserManager
    @Before
    fun initTests() {
    MockKAnnotations.init(this)
    userManager = UserManager(userApi)
    every { user.userId } returns userId
    }

    View Slide

  56. @Test
    fun basicTest() = runBlocking {
    }

    View Slide

  57. @Test
    fun basicTest() = runBlocking {
    every { user.userId } returns userId
    }

    View Slide

  58. @Test
    fun basicTest() = runBlocking {
    every { user.userId } returns userId
    coEvery { userApi.deleteUser(userId) } returns true
    }

    View Slide

  59. @Test
    fun basicTest() = runBlocking {
    every { user.userId } returns userId
    coEvery { userApi.deleteUser(userId) } returns true
    val result = userManager.deleteUser(user)
    }

    View Slide

  60. @Test
    fun basicTest() = runBlocking {
    every { user.userId } returns userId
    coEvery { userApi.deleteUser(userId) } returns true
    val result = userManager.deleteUser(user)
    assert(result).toBe(true)
    coVerify { userApi.deleteUser(userId) }
    }

    View Slide

  61. Other features
    ● Spy
    ● Constructor mock
    ● Extension function mock
    ● Enumeration mock

    View Slide

  62. Limitations
    ● final static
    ● inline functions

    View Slide

  63. Resources
    ● https://mockk.io/
    ● Mocking is not rocket science: Basics:
    https://blog.kotlin-academy.com/mocking-is-not-rocke
    t-science-basics-ae55d0aadf2b

    View Slide

  64. Unit tests without integration tests (Obligatory gif)

    View Slide

  65. Questions?

    View Slide

  66. Hadi Tok, CitizenMe
    @hadi_tok
    Thank you!

    View Slide