MockK - метод расширения верхнего уровня mock / spy, вызываемый на val верхнего уровня - PullRequest
0 голосов
/ 27 ноября 2018

В MWE ниже я пытаюсь проверить, что вызов baz() также вызывает метод для другого объекта.Тем не менее, я не могу посмеяться над этим объектом.

MWE:

package com.example

import io.mockk.every
import io.mockk.mockkStatic
import io.mockk.spyk
import io.mockk.verify
import org.junit.jupiter.api.Test

class FooBarTest {
    @Test
    fun `top level fun baz() calls theVal_bar()`() {
        mockkStatic("com.example.FooBarTestKt")
        val spy = spyk(theVal, name = "Hello, Spy!")

        every { theVal } returns spy

        // Should call bar() on the spy, but note that the spy's name is not printed
        baz()

        verify { spy.bar() }
    }
}

class Foo

fun Foo.bar() = println("Foo.bar! name = $this")

val theVal = Foo()

fun baz() = theVal.bar()

Это не удается, потому что вызов theVal.bar() получает вместо этого значение инициализатора valсмоделированного значения spy.

Как можно принудительно использовать используемый шпион, не меняя определения свойств верхнего уровня?Другими словами: мне нужен верхний уровень «константа», но я тоже хочу издеваться над ним.Я мог бы использовать val theVal get() = Foo(), который решает проблему, но он значительно меняет код, поскольку каждый раз заменяет экземпляр Foo.

Используемые версии: - Kotlin 1.3.10 - MockK 1.8.13.kotlin13 - JUnit 5.3.1

Ошибка:

java.lang.AssertionError: Verification failed: call 1 of 1: class com.example.FooBarTestKt.bar(eq(Foo(Hello, Spy!#1)))). Only one matching call to FooBarTestKt(static FooBarTestKt)/bar(Foo) happened, but arguments are not matching:
[0]: argument: com.example.Foo@476b0ae6, matcher: eq(Foo(Hello, Spy!#1)), result: -

Ответы [ 2 ]

0 голосов
/ 28 ноября 2018

О, это действительно безумие, когда речь идет о статических и объектных макетах и ​​функциях расширения.Чтобы выжить, просто думайте о расширенных функциях как о статических функциях с аргументом.

Проверьте, это работает, потому что fooInstance - это просто объект, переданный в качестве первого аргумента:

    mockkStatic("kot.TestFileKt")

    baz()

    val fooInstance = theVal

    verify { fooInstance.bar() }

Объединение егоне работает:

    verify { theVal.bar() }

, потому что это также хорошо проверено.

Это также будет работать (как я сказал Foo - это просто первый аргумент статического метода):

    mockkStatic("kot.TestFileKt")

    baz()

    verify { any<Foo>().bar() }
0 голосов
/ 27 ноября 2018

Вместо использования инициализатора, используйте вспомогательное (личное) свойство и используйте get() для val, который нужно смоделировать:

private val _theVal = Foo()
val theVal get() = _theVal

Использование геттера вместоИнициализатор создает метод получения без статического поля поддержки.Вы можете проверить байт-код, чтобы увидеть это:

Kotlin:

package com.example

@JvmField // See also: https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#instance-fields
val thisIsAField = "I'm static!"

val thisIsAValWithInitialiser = "I'm a static field too!"

val thisIsAValWithGetter get() = "I'm hardcoded in the getter method!"

Байт-код (я удалил большую часть беспорядка, чтобы мою точку зрения было легче увидеть):

public final static Ljava/lang/String; thisIsAField

private final static Ljava/lang/String; thisIsAValWithInitialiser

public final static getThisIsAValWithInitialiser()Ljava/lang/String;
L0
LINENUMBER 6 L0
GETSTATIC com/example/FooBarTestKt.thisIsAValWithInitialiser : Ljava/lang/String;
ARETURN
L1

public final static getThisIsAValWithGetter()Ljava/lang/String;
L0
LINENUMBER 8 L0
LDC "I'm hardcoded in the getter method!"
ARETURN
L1

static <clinit>()V
L0
LINENUMBER 4 L0
LDC "I'm static!"
PUTSTATIC com/example/FooBarTestKt.thisIsAField : Ljava/lang/String;
L1
LINENUMBER 6 L1
LDC "I'm a static field too!"
PUTSTATIC com/example/FooBarTestKt.thisIsAValWithInitialiser : Ljava/lang/String;
RETURN

Что вы можете увидеть здесь?Между thisIsAField и thisIsAValWithInitialiser есть важное сходство, заключающееся в том, что они поддерживаются статическими полями.Метод получения thisIsAValWithInitialiser просто возвращает это значение.Значение равно private.

Сходство между thisIsAValWithInitialiser и thisIsAValWithGetter состоит в том, что они оба являются публичными методами получения, но разница в том, что возвращаемое значение thisIsAValWithGetter жестко закодировано в теле метода,Это просто публичный метод, который MockK может переопределить (хотя он и является окончательным).

Я предполагаю (как я не знаю внутреннее устройство), что MockK не может отменять GETSTATIC com/example/FooBarTestKt.thisIsAValWithInitialiser : Ljava/lang/String;, поэтому val инициализатор не может быть посмешищем.

...