21

Lets say we have a class like this:

class Whatever {
    private var something = false

    fun aMethod(): Int {
        return if( something ) {
            1
        } else {
            0
        }
    }
}

According to the documentation, it looks like I should be able to do the following:

val classUnderTest = spyk(Whatever())

every { classUnderTest getProperty "something" } returns true

assertThat(classUnderTest.aMethod()).isEqualTo(1)

but instead I get the error: io.mockk.MockKException: Missing calls inside every { ... } block

I'm using mockk 1.8.5, kotlin 1.2.51

Sylhare
  • 5,907
  • 8
  • 64
  • 80
Maalevolent
  • 472
  • 1
  • 5
  • 12

4 Answers4

14

Try to use answers instead of returns, like this:

val mock = spyk(MockCls(), recordPrivateCalls = true)

every { mock.property } answers { fieldValue }
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
}
Sylhare
  • 5,907
  • 8
  • 64
  • 80
Vova
  • 956
  • 8
  • 22
6

You can use this small extension

fun Any.mockPrivateFields(vararg mocks: Any): Any {
    mocks.forEach { mock ->
        javaClass.declaredFields
                .filter { it.modifiers.and(Modifier.PRIVATE) > 0 || it.modifiers.and(Modifier.PROTECTED) > 0 }
                .firstOrNull { it.type == mock.javaClass}
                ?.also { it.isAccessible = true }
                ?.set(this, mock)
    }
    return this
}

it set the value of the first field of the same type passed as parameter, and can use multiple parameters, for instance:

myObject.mockPrivateFields(1, "hi!")

it will mock the first Int field found, and the first String field found.

This is only for private/protected fields.

You can modify it to support multiple fields of the same type. I didn't try it with generics but I guess it is not difficult to support it.

Sylhare
  • 5,907
  • 8
  • 64
  • 80
Ignacio Tomas Crespo
  • 3,401
  • 1
  • 20
  • 13
1

Elaborating on @Ignacio's answer:

inline fun <reified T> T.setPrivateField(field: String, value: Any): T  = apply {
    T::class.java.declaredFields
        .find { it.name == field}
        ?.also { it.isAccessible = true }
        ?.set(this, value)
}

Please note that this is technically not "mocking", you're actually setting the value in the instance, not intercepting the call to it.

m0skit0
  • 25,268
  • 11
  • 79
  • 127
0

This happens probably because there is no getter/setter created for your something property by the kotlin compiler.

What if you modified it to be like the following?

class Whatever {
    private val something
        get() = false

    fun aMethod(): Int {
        return if( something ) {
            1
        } else {
            0
        }
    }
}
Giorgos Kylafas
  • 2,243
  • 25
  • 25
  • Change code for test is a wrong way – Vova Feb 21 '23 at 08:29
  • @Vova I hear what you're saying, but this is not always true. For example, one could argue that using dependency injection in our code _is_ a kind of "changing code for the sake of test(ability)". – Giorgos Kylafas Feb 22 '23 at 11:45
  • I preferce the defenition dependency inversion, and using the DI in code is a kind of "common sence", and yes the DI has the positive side effect make code more testable – Vova Feb 25 '23 at 19:42