Created
December 25, 2023 03:13
-
-
Save aoriani/17ddeca36eda74d7eb4caf3abd8ce56d to your computer and use it in GitHub Desktop.
A simple implementation of a mock library using ByteBuddy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.example | |
import net.bytebuddy.ByteBuddy | |
import net.bytebuddy.dynamic.DynamicType | |
import net.bytebuddy.implementation.FixedValue | |
import net.bytebuddy.matcher.ElementMatchers | |
import org.junit.jupiter.api.Test | |
import java.net.Socket | |
import kotlin.reflect.KFunction | |
import kotlin.reflect.jvm.javaMethod | |
import kotlin.test.assertEquals | |
import kotlin.test.assertTrue | |
interface MockScopeDsl { | |
fun <T> whenever(method: KFunction<T>): KFunction<T> | |
infix fun <T> KFunction<T>.returns(returnValue: T) | |
} | |
class MockScopeDslImpl : MockScopeDsl { | |
val mockedMethods = mutableMapOf<KFunction<*>, Any?>() | |
override fun <T> whenever(method: KFunction<T>) = method | |
override fun <T> KFunction<T>.returns(returnValue: T) { | |
mockedMethods[this@returns] = returnValue | |
} | |
} | |
inline fun <reified T : Any> mock(configBlock: MockScopeDsl.() -> Unit): T { | |
val mockScope = MockScopeDslImpl() | |
mockScope.configBlock() | |
var subClassProto: DynamicType.Builder<T> = ByteBuddy().subclass(T::class.java) | |
mockScope.mockedMethods.forEach { (kFunction, returnValue) -> | |
subClassProto = subClassProto | |
.method(ElementMatchers.`is`(kFunction.javaMethod!!)) | |
.intercept(FixedValue.value(returnValue!!)) | |
} | |
val clazz = subClassProto.make().load(T::class.java.classLoader).loaded as Class<T> | |
return clazz.getDeclaredConstructor().newInstance() | |
} | |
class SocketTest { | |
private val mockedSocket: Socket = mock { | |
whenever(Socket::getLocalPort) returns 42 | |
whenever(Socket::getInputStream) returns "foo bar".byteInputStream() | |
whenever(Socket::isBound) returns true | |
} | |
@Test | |
fun test() { | |
assertEquals(42, mockedSocket.localPort) | |
assertTrue(mockedSocket.isBound) | |
assertEquals("foo bar", mockedSocket.getInputStream() | |
.bufferedReader() | |
.use { it.readText() } | |
) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment