Kotlinシングルトンオブジェクトと、そのメソッドを呼び出すfunが与えられた場合
_object SomeObject {
fun someFun() {}
}
fun callerFun() {
SomeObject.someFun()
}
_
SomeObject.someFun()
の呼び出しを模擬する方法はありますか?
オブジェクトをインターフェイスに実装するだけで、モックライブラリでオブジェクトをモックできます。 Junit + Mockito + Mockito-Kotlin の例:
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import org.junit.Assert.assertEquals
import org.junit.Test
object SomeObject : SomeInterface {
override fun someFun():String {
return ""
}
}
interface SomeInterface {
fun someFun():String
}
class SampleTest {
@Test
fun test_with_mock() {
val mock = mock<SomeInterface>()
whenever(mock.someFun()).thenReturn("42")
val answer = mock.someFun()
assertEquals("42", answer)
}
}
または、SomeObject
の中にcallerFun
をモックする場合:
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import org.junit.Assert.assertEquals
import org.junit.Test
object SomeObject : SomeInterface {
override fun someFun():String {
return ""
}
}
class Caller(val someInterface: SomeInterface) {
fun callerFun():String {
return "Test ${someInterface.someFun()}"
}
}
// Example of use
val test = Caller(SomeObject).callerFun()
interface SomeInterface {
fun someFun():String
}
class SampleTest {
@Test
fun test_with_mock() {
val mock = mock<SomeInterface>()
val caller = Caller(mock)
whenever(mock.someFun()).thenReturn("42")
val answer = caller.callerFun()
assertEquals("Test 42", answer)
}
}
Kotlin用の非常に素晴らしいモックライブラリがあります- Mockk 。これは、オブジェクトをモックすることができます。
ドキュメントの時点で:
オブジェクトは次の方法でモックに変換できます。
object MockObj {
fun add(a: Int, b: Int) = a + b
}
mockkObject(MockObj) // aplies mocking to an Object
assertEquals(3, MockObj.add(1, 2))
every { MockObj.add(1, 2) } returns 55
assertEquals(55, MockObj.add(1, 2))
元に戻すには、unmockkAllまたはunmockkObjectを使用します。
@Before
fun beforeTests() {
mockkObject(MockObj)
every { MockObj.add(1,2) } returns 55
}
@Test
fun willUseMockBehaviour() {
assertEquals(55, MockObj.add(1,2))
}
@After
fun afterTests() {
unmockkAll()
// or unmockkObject(MockObj)
}
Kotlin言語の制限にもかかわらず、テストロジックで必要な場合は、オブジェクトの新しいインスタンスを作成できます。
val newObjectMock = mockk<MockObj>()
クラスdelegatesを使用すると、追加のライブラリなしでオブジェクトをモックできます。
これが私の提案です
val someObjectDelegate : SomeInterface? = null
object SomeObject: by someObjectDelegate ?: SomeObjectImpl
object SomeObjectImpl : SomeInterface {
fun someFun() {
println("SomeObjectImpl someFun called")
}
}
interface SomeInterface {
fun someFun()
}
テストでは、動作を変更するデリゲートオブジェクトを設定できます。そうしないと、実際の実装が使用されます。
@Beofre
fun setUp() {
someObjectDelegate = object : SomeInterface {
fun someFun() {
println("Mocked function")
}
}
// Will call method from your delegate
SomeObject.someFun()
}
もちろん上記の名前は悪いですが、例のために目的を示しています。
SomeObjectが初期化された後、デリゲートはすべての機能を処理します。
詳細については、公式の ドキュメント をご覧ください。
非常に便利な Mockk ライブラリを使用するほかに、Mockitoとリフレクションを使用してobject
を単純にモックできます。 Kotlinオブジェクトは、プライベートコンストラクターとINSTANCE
静的フィールドを持つ通常のJavaクラスです。リフレクションを使用すると、INSTANCE
の値をモック付きに置き換えることができますオブジェクト。テスト後、変更が他のテストに影響を与えないように、オリジナルを復元する必要があります
Mockito Kotlinの使用(説明されているように拡張構成を追加する必要があります here 模擬最終クラスに):
testCompile "com.nhaarman:mockito-kotlin:1.5.0"
最初の楽しみは、INSTANCE
クラスのstatic object
フィールドの値を置き換え、前の値を返すことです。
fun <T> replaceObjectInstance(clazz: Class<T>, newInstance: T): T {
if (!clazz.declaredFields.any {
it.name == "INSTANCE" && it.type == clazz && Modifier.isStatic(it.modifiers)
}) {
throw InstantiationException("clazz ${clazz.canonicalName} does not have a static " +
"INSTANCE field, is it really a Kotlin \"object\"?")
}
val instanceField = clazz.getDeclaredField("INSTANCE")
val modifiersField = Field::class.Java.getDeclaredField("modifiers")
modifiersField.isAccessible = true
modifiersField.setInt(instanceField, instanceField.modifiers and Modifier.FINAL.inv())
instanceField.isAccessible = true
val originalInstance = instanceField.get(null) as T
instanceField.set(null, newInstance)
return originalInstance
}
その後、object
のモックインスタンスを作成し、元の値をモックされた値で置き換え、後でリセットできるように元の値を返すという楽しみがあります。
fun <T> mockObject(clazz: Class<T>): T {
val constructor = clazz.declaredConstructors.find { it.parameterCount == 0 }
?: throw InstantiationException("class ${clazz.canonicalName} has no empty constructor, " +
"is it really a Kotlin \"object\"?")
constructor.isAccessible = true
val mockedInstance = spy(constructor.newInstance() as T)
return replaceObjectInstance(clazz, mockedInstance)
}
コトリンシュガーを加える
class MockedScope<T : Any>(private val clazz: Class<T>) {
fun test(block: () -> Unit) {
val originalInstance = mockObject(clazz)
block.invoke()
replaceObjectInstance(clazz, originalInstance)
}
}
fun <T : Any> withMockObject(clazz: Class<T>) = MockedScope(clazz)
そして最後に、object
object Foo {
fun bar(arg: String) = 0
}
この方法でテストできます
withMockObject(Foo.javaClass).test {
doAnswer { 1 }.whenever(Foo).bar(any())
Assert.assertEquals(1, Foo.bar(""))
}
Assert.assertEquals(0, Foo.bar(""))
バイトコードを操作するのではなく、コードを変更する意思がなければ、答えはノーです。 callerFun
のSomeObject.someFun()
への呼び出しをモックする最も簡単な方法(および私が推奨する方法)は、モックオブジェクトをスリップする方法を提供することです。
例えば.
object SomeObject {
fun someFun() {}
}
fun callerFun() {
_callerFun { SomeObject.someFun() }
}
internal inline fun _callerFun(caller: () -> Unit) {
caller()
}
ここでのアイデアは、あなたが変えたいと思うものを変えることです。シングルトンとそのシングルトンに作用するトップレベル関数が必要な場合は、上に示したように、パブリック署名を変更せずにトップレベル関数をテスト可能にする方法は、実装をinternal
関数に移動することですモックを滑らせることができます。