Mockito 2を使用してKotlin最終クラスを模擬できません。さらに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);
}
}
_
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
依存関係を使用しているため、最終クラスを自動的にモックできるようになっています。
たとえば、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
}
PowerMockは独自のMockMaker
を実装しているため、たとえPowerMockが依存関係として追加され、使用されていない場合でも、Mockito mock-maker-inlineとの互換性がなくなります。パスに2つのorg.mockito.plugins.MockMaker
が存在する場合、どれか1つしか使用できません。
ただし、PowerMockは別のMockMakerへの呼び出しを委任することができ、その場合、テストはPowerMockなしで実行されます。 PowerMock 1.7.0以降、これはPowerMock構成を使用して構成できます。
MockMakerは、ファイルorg/powermock/extensions/configuration.properties
を作成して次のように設定することで構成できます。
mockito.mock-maker-class=mock-maker-inline
PowerMockでMockito mock-maker-inlineを使用する例: https://github.com/powermock/powermock-examples-maven/tree/master/mockito2
Mockito 2.1.0以降、最終的な型、列挙型、最終的なメソッドをモックする可能性があります。元の質問へのコメントですでに言及されていました。
これを行うには、フォルダー(存在しない場合)test/resources/mockito-extensions
を作成し、org.mockito.plugins.MockMaker
という名前のファイルを追加して、次の行を追加する必要があります。
mock-maker-inline
documentation および tutorial へのリンク
以下の依存関係をbuild.gradleに追加してみてください。
testImplementation 'org.mockito:mockito-inline:2.8.47'
2.8.47の代わりにmockitoバージョンに置き換えます。これは、問題にpowermockを使用しないようにするのに役立ちます。
以下のリンクを見て、このことがどのように機能するかを確認してください。
mock-maker-inline
は、他の回答で指摘されているように機能します。しかし、それは非常に遅いです。 all-open
この問題を回避するためのプラグイン。
これを行うには、次のものが必要です。
annotation class Mockable
build.gradle
ファイル:dependencies {
classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version"
}
apply plugin: 'kotlin-allopen'
allOpen {
annotation('com.example.Mockable')
}
@Mockable
class Foo {
fun calculateTheFoo(): Int {
sleep(1_000) // Difficult things here
return 1
}
}
さらに詳しい情報が必要な場合は、私のブログ記事を読んで詳細を説明することができます。 Mockitoを使用したKotlinクラスのモックアップ—高速な方法
Kotlinは、デコレータパターンをシンプルかつ簡潔に実装します。
_open class OpenClass() : SomeInterface by FinalClass()
_
これにより、実際には、各SomeInterface
メンバーが、open
ラッパークラスのFinalClass()
インスタンスへのラップされた呼び出しでオーバーライドされます。次に、このラッパーをテストに挿入できます。
ターゲットクラスがライブラリに存在するプロジェクトでは、これに頼らなければならず、_all-open
_コンパイラプラグインでは変更できないように見えました。
実装ではなく、インターフェースにプログラムしてみましょう。インターフェイスを抽出し、コードで使用して、モックすることができます。たとえば、次は機能しません。
import com.nhaarman.mockito_kotlin.mock
class MyFinalClass {...}
(snip)
private val MyFinalClass = mock()
それではインターフェースを抽出しましょう:
class MyFinalClass : MyInterface {...}
(snip)
private val MyInterface = mock()
Kotlinでは、すべてのクラスがデフォルトでfinalであるためです。
クラス宣言にopen
を追加することも検討する必要があります。
例:open class MyClasss{}