:dove_of_peace: Seems like announcing mocking fram...
# multiplatform
n
🕊️ Seems like announcing mocking frameworks compatible with Kotlin/Native and Kotlin Multiplatform is all the rage these days, and as such, I too wish to present a mocking framework, called Mockative, with these notable highlights and features: • Supports effortless multithreading in coroutines • No default return values • Concise, non-intrusive API • Supports both expression and matcher based stubbing • Fully type-safe • Mocking of interfaces Testing with Mockative couldn’t be simpler, as all that’s required is annotating a property with
@Mock
and assigning it to the value of a call to the
<T> mock(KClass<T>)
function:
Copy code
class GitHubServiceTests {
    @Mock
    val api = mock(GitHubAPI::class)
}
Please see the README for instructions on how to get started using Mockative today. We would love to hear your feedback on the framework.
👍 3
K 10
b
Your sample could be much more fluid with a simple helper inline fun
Copy code
inline fun <reified T> mock() = mock(T::class)

class GitHubServiceTests {
    @Mock
    val api: GitHubAPI = mock()
    @Mock
    val api2 = mock<GitHubAPI>()
}
👍 1
n
Interesting, thanks! I’ll have to have a look at that 😊
e
you may want
typeOf<T>()
? as
T::class
does not carry any information about type parameters
n
I had a look at your proposal @Big Chungus, and while I really like the concise syntax, it unfortunately does not work, because Mockative uses a specific quirk of the Kotlin compiler that enables selecting a generated function override at compile time. While using an
inline
function with a
reified
type parameter appears compatible at first sight, the bytecode turns out different in a way that leaves it incompatible. Thank you for the suggestions though 🙂!
👍 1
e
how about generating functions with any
mock*()
name, so that you can write
Copy code
class Test {
    @Mock val x: Foo<X> = mockFooX()
    @Mock val y: Foo<Y> = mockFooY()
}
?
n
@ephemient. The type information is used to determine which interface to generate an implementation for, as well as selecting the right overload at compile time. You just highlighted a shortcoming of Mockative though: We don’t support mocking functions accepting generic type arguments.
typeOf<T>()
returns a
KType
with no type information (at compile time), and as such, will not work with Mockative. We’ll have a look into whether we can support generic types.
e
can you just generate a different
mock*()
function for every
@Mock
encountered in the source code?
I'm not sure what you're relying on that results in inline functions not working, though...
n
Feel free to check out the repository and have a look at the resulting bytecode 🙂
e
I see resolveUsageSyntax but it doesn't rely on bytecode
n
Have a look at the bytecode for a property annotated with
@Mock
, which calls
<T> mock(KClass<T>)
. This is where the quirk is useful and where an
inline
function prevents things from working
e
just to be clear, by "bytecode" you mean JVM bytecode? because I see no reference to that, and in fact you shouldn't be getting that in KSP
n
In regards to your
mock*()
question, then yes, we absolutely could generate a function for each mock, and in fact we’re already generating a class for each mock accepting 0 arguments, with the suffix
Mock
. These can be used in place of the result of a call to
<T> mock(KClass<T>)
, if you so desire
I mean the JVM bytecode, yes. Mockative doesn’t generate any bytecode, but the bytecode generated by the Kotlin compiler turns out different when we substitute a call to
fun <T> mock(KClass<T>)
with a call to
inline fun <reified T> mock()
, because overload resolution with generated code appears to work differently with inline functions. Mockative uses a quirk of the Kotlin compiler where it will select the generated
mock(KClass<GitHubAPI>)
function over the generic
mock(KClass<T>)
function, as long as they’re in the same package. Please check out the repository and see the effect for yourself, and feel free to submit a Pull Request if you come up with a more concise way that remains compatible 🙂
e
once you find a @Mock-annotated KSPropertyDeclaration, if RHS is a simple method call, then generate a function by that name. then there's no issue with overloading
for the reified inline method to work, you can to aggregate the generated mocks, produce a mapping of KClass<T> to { TMock() }, and make the mock() method simply look up from it
💡 1
🤔 1
👍 1
n
That was considered as well, but how will you populate this map in Kotlin/Native?
You might be on to something here… 😅
e
map should work fine?
@ThreadLocal
should be safe if you're not on the new memory manager
alternately I suppose you could generate a
mock()
method with a big
when
statement inside 😆
b
RE: inline. I think it would work if you'd replace current mock function with inline variant and would generate inline variants as well (I think kotlin compiler prefers noninline overloads over inline, but if everything's inline then we're on equal ground).