Нельзя издеваться над финальным классом Kotlin, используя Mockito 2

Я не могу издеваться над финальным классом Kotlin с использованием Mockito 2. Кроме того, я использую Robolectric.

Это мой тестовый код:

@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
public class Test {

    // more mocks

    @Mock
    MyKotlinLoader kotlinLoader;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
    }
}

Тест не работает, когда мы пытаемся инициализировать mocks в методе setUp().

Кроме того, я использую следующие gradle зависимости в моем коде:

testCompile 'org.robolectric:robolectric:3.3.2'
testCompile 'org.robolectric:shadows-multidex:3.3.2'
testCompile 'org.robolectric:shadows-support-v4:3.3.2'
testCompile("org.powermock:powermock-api-mockito2:1.7.0") {
    exclude module: 'hamcrest-core'
    exclude module: 'objenesis'
}
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-inline:2.8.9'

Все остальные модульные тесты проходят с использованием этой конфигурации, но как только я пытаюсь высмеять класс Kotlin, он выдает следующую ошибку:

Mockito cannot mock/spy because : - final class

Обратите внимание, что я использую Mockito-версию 2, и я использую зависимость inline, которая автоматически дает возможность имитировать окончательные классы.

Ответ 1

Вы можете использовать Powermock для этого, например:

import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.spy;
import static org.powermock.api.mockito.PowerMockito.when;

@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
@PowerMockIgnore({ "org.mockito.*", "org.robolectric.*", "android.*" })
@PrepareForTest({FinalClass1.class, FinalClass2.class})
public class Test {
    @Rule
    public PowerMockRule rule = new PowerMockRule();

    ... // your code here
}

Ответ 2

PowerMock реализует свой собственный MockMaker, что приводит к несовместимости с Mockito mock-maker-inline, даже если PowerMock просто добавлен как зависимость и не используется. Если в пути существует два org.mockito.plugins.MockMaker, то может использоваться только один, который не определен.

Однако PowerMock делегирует вызовы другому MockMaker, а затем тесты запускаются без PowerMock. Поскольку PowerMock 1.7.0 можно настроить с помощью конфигурации PowerMock.

MockMaker можно настроить, создав файл org/powermock/extensions/configuration.properties и установив:

mockito.mock-maker-class=mock-maker-inline

Пример использования Mockito mock-maker-inline с PowerMock: https://github.com/powermock/powermock-examples-maven/tree/master/mockito2

Ответ 3

Давайте программируем интерфейсы, а не реализации. Вы можете извлечь интерфейс, использовать его в своем коде и издеваться над ним. Например, следующее не будет работать:

import com.nhaarman.mockito_kotlin.mock
class MyFinalClass {...}
(snip)
private val MyFinalClass = mock()

Итак, давайте извлечем интерфейс:

class MyFinalClass : MyInterface {...}
(snip)
private val MyInterface = mock()

Ответ 4

Потому что в kotlin все классы являются окончательными по умолчанию.

Вам также следует рассмотреть возможность добавления open к объявлению класса.

Пример: open class MyClasss{}

Ответ 5

Kotlin делает шаблон декоратора простым и лаконичным для реализации:

open class OpenClass() : SomeInterface by FinalClass()

Это, по сути, переопределит каждый из членов SomeInterface с обернутыми вызовами к FinalClass() в вашем классе open оболочки. Затем вы можете добавить эту оболочку в свои тесты.

Мне пришлось прибегнуть к этому в проекте, где целевой класс жил в библиотеке и, казалось, был неприкосновен all-open плагином компилятора.

Ответ 6

Попробуйте добавить эту зависимость ниже в ваш build.gradle.

testImplementation 'org.mockito:mockito-inline:2.8.47'

Замените вашей версией mockito вместо 2.8.47. Это поможет вам избежать использования Powermock для решения этой проблемы.

Вы можете посмотреть в ссылке ниже, как эта штука работает.

Как издеваться над финальным классом с мокито