Нельзя издеваться над финальным классом 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 для решения этой проблемы.
Вы можете посмотреть в ссылке ниже, как эта штука работает.
Как издеваться над финальным классом с мокито