lupuuss / mokkery Goto Github PK
View Code? Open in Web Editor NEWThe mocking library for Kotlin Multiplatform, easy to use, boilerplate-free and compiler plugin driven.
Home Page: https://mokkery.dev
License: Apache License 2.0
The mocking library for Kotlin Multiplatform, easy to use, boilerplate-free and compiler plugin driven.
Home Page: https://mokkery.dev
License: Apache License 2.0
Kotlin 2.0.20-Beta1
seems to introduce multiple breaking changes that seems trivial to fix. However, fixing them in a backward compatible way might not be possible.
Sometimes you need to test a state that is present while a suspending call is being made. For example, loading is true while the suspending call is still being executed.
So you need a way to have a mocked suspend function not return immediately but only return when you tell it to. Here is the way i am handling it now, but it would be nice to have support for this built-in
I created a mutable map to record what calls are being blocked:
val deferred = mutableMap<Any, CompletableDeferred<Unit>>()
fun Any.whilePaused(block: () -> Unit) {
try {
deferred[this] = CompletableDeferred()
block()
}
finally {
deferred.remove(this)?.complete(Unit)
}
}
Then in any suspend mock set up that i need to support blocking I have to do this (In this case it is a fun interface
being mocked):
val fooBar =
mock<FooBar> {
everySuspend { invoke(any()) } calls {
success(returnValue)
.also { deferred[this@mock]?.await() }
}
}
This then lets me do tests like this:
fooBar.whilePaused {
sut.doLoad()
assertTrue(sut.loading)
}
assertFalse(sut.loading)
Hello!
I've found an issue with verifying the objects when the contents are the same but instances are not.
Here is the error message:
Calls to the same method with failing matchers:
ByteArrayParser(1).doNothing(id = UUID@fcb4004, value = [72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33])
[-] id:
expect: UUID@8c11eee
actual: UUID@fcb4004
[-] value:
expect: [72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33]
actual: [72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33]
As one can see the contents of byte array are the same. Also I am using the any()
matcher so it should not matter if the contents are the same at the first place.
The minimal reproducible example is attached.
multiplatform-library-template.zip
Tested on Mac with apple chip and on windows
It seems that using value class, e.g. kotlin.Result
in mocks will fail the test with exception
Given the interface
interface TestInterfaceWithValueClass {
fun test(): Result<String>
}
Calling the test
@Test
fun testReturnsMockableInterfaceWithValueClassImpl() {
val mocked = mock<TestInterfaceWithValueClass> {
every { test() } returns Result.success("test")
}
assertEquals(Result.success("test"), mocked.test())
}
Will fail with exception
java.lang.NullPointerException: Cannot invoke "kotlin.Result.unbox-impl()" because the return value of "dev.mokkery.internal.MokkeryInterceptor.interceptCall(dev.mokkery.internal.CallContext)" is null
at dev.mokkery.test.TestInterfaceWithValueClassb7d4c11efdfd4d2aa2c67574e6dd5d5eMock.test-d1pmJ48(MockTest.kt)
at dev.mokkery.test.MockTest$testReturnsMockableInterfaceWithValueClassImpl$mocked$1$1.invoke-IoAF18A(MockTest.kt:23)
at dev.mokkery.test.MockTest$testReturnsMockableInterfaceWithValueClassImpl$mocked$1$1.invoke(MockTest.kt)
at dev.mokkery.internal.EveryKt.internalEvery(Every.kt:21)
at dev.mokkery.test.MockTest$testReturnsMockableInterfaceWithValueClassImpl$mocked$1.invoke(MockTest.kt)
at dev.mokkery.test.MockTest$testReturnsMockableInterfaceWithValueClassImpl$mocked$1.invoke(MockTest.kt:22)
at dev.mokkery.test.TestInterfaceWithValueClassb7d4c11efdfd4d2aa2c67574e6dd5d5eMock.<init>(MockTest.kt)
at dev.mokkery.test.MockTest.testReturnsMockableInterfaceWithValueClassImpl(MockTest.kt:22)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
...
mokkery version: 1.9.23-1.6.0
kotlin version: 1.9.23
mokkery: 2.1.1
ksp: 2.0.20-Beta2-1.0.23
kotlin: 2.0.20-Beta2
When attempting migration to the latest kotlin/ksp versions we started noticing the following issue when running ./gradlew :shared:compileTestKotlinIosArm64
e: java.lang.NoSuchMethodError: 'org.jetbrains.kotlin.diagnostics.rendering.ContextIndependentParameterRenderer org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.getRENDER_TYPE()'
at dev.mokkery.plugin.diagnostics.MokkeryDiagnosticRendererFactory.<init>(MokkeryDiagnosticRendererFactory.kt:16)
at dev.mokkery.plugin.MokkeryCompilerPluginRegistrar.registerExtensions(MokkeryCompilerPluginRegistrar.kt:21)
full log: mokkery-error.log
In most of my use of Mocking I set up mocks without checking parameters of the call in the setup and instead check parameters in verification. It would be nice to have a way to define that without having to specify all the any()
parameters. The way that could be done is with method references
So from the examples:
val repository = mock<BookRepository> {
everySuspend { findById(any()) } calls { (id: String) -> Book(id) }
}
Would be able to be replaced by:
val repository = mock<BookRepository> {
everySuspend(this::findById) calls { (id: String) -> Book(id) }
}
During solving migration issues another issue come up, previously passing test start failing. I narrow down the issue, and it looks like mocked interface doesn't return the value already set with every. Maybe it is again some behavioral difference of Mokkery but couldn't be sure.
You can see the code piece in the PR
Let me know if anything is needed from my end!
Currently, Mokkery requires abstract
/open
classes to have a default constructor, and it should be possible to remove this requirement.
This limitation was partially mentioned in #4 .
I have a lot of boilerplate mocks set up like this where I have a bunch of var properties defining the data which can then be modified and then would like to use returns/returnsSuccess with the property instead of call.
So using the example from the docs it currently looks something like this:
var book = Book(1)
val repository = mock<BookRepository> {
everySuspend { findById(any()) } calls { book }
}
reading the code it doesn't really represent what it is doing. calls is only used because we need to get the current value of the property. And when the return value is result there is no callsSuccess
So my thought would be that there would be overloads of returns/returnsSuccess that instead of a value of type T take a lambda of type () -> T
Then one could instead say something more like
val repository = mock<BookRepository> {
everySuspend { findById(any()) } returns { book }
}
or
val repository = mock<BookRepository> {
everySuspend { findById(any()) } returns(::book)
}
and for the case where the type is a Result
val repository = mock<BookRepository> {
everySuspend { findById(any()) } returnsSuccess { book }
}
AutoUnit does not work for functional types.
The first test from the example below will fail, while the second one is successful.
Kotlin multiplatform version: 1.9.24
Kotlin multiplatform target: js
Mokkery version: 1.9.24-1.7.0
interface TestInterface {
fun invokeInterface(foo: Int)
}
class Example {
@Test
fun testAutoUnitDoesNotWorkForFunction() {
val functionMock = mock<(foo: Int) -> Unit>(MockMode.autoUnit)
functionMock.invoke(1) // Fails with CallNotMockedException: Call Function1(1).invoke(p1 = 1) not mocked!
}
@Test
fun testAutoUnitWorkForInterface() {
val functionMock = mock<TestInterface>(MockMode.autoUnit)
functionMock.invokeInterface(1) // success
}
}
I have the implementation of this data class:
@Serializable
data class WordPair(
val wordsTotal: Int,
val wordsFound: List<String>
)
at first run, I get this error:
Type ''com.alejandrorios.bogglemultiplatform.data.models.WordPair'' is final and cannot be used with ''mock''!
which I know it is because of one of the limitations of the library, then I'm including the @OpenForMokkery
annotation to the data class, and now I get this error:
Class ''com.alejandrorios.bogglemultiplatform.data.models.WordPair'' has no default constructor and cannot be used with ''mock''!
Is it possible to solve this without adding default values to the data class?, maybe I'm missing something else.
Hey team Mokkery,
This is more of an information request than an issue, just wondering, if and when we can expect support for Kotlin 2.0?
I'm aware it's still in beta, but we'd love to start testing it asap, but since mokkery is a crucial part of the application we're a bit blocked.
Best,
-martin
Hello,
Thank you for this amazing mocking library, thanks to it, I get rid of ksp and decreased my build times, also finally I was able to move my project to Kotlin 2.0.
During the migration from Mockative to Mokkery I had various issues and couldn't fix 3 of them. This issue is targeting to one of it.
If you check the linked PR, I have commented out one verify
line. This line was working fine with Mockative, I had to comment it during Mokkery migration since it was giving the below error:
java.lang.NullPointerException at AdControlRepositoryTest.kt:225
You can see the full log in the link I shared below.
Let me know if anything is needed from my end!
Log: https://github.com/Oztechan/CCC/actions/runs/9614886435/job/26546544559?pr=3581
PR: Oztechan/CCC#3581
Currently, Mokkery does not support coroutines for Wasm-WASI because coroutines do not support this target. Support for this target will be available with coroutines version 1.9.0.
Hello,
Thank you for this amazing mocking library, thanks to it, I get rid of ksp and decreased my build times, also finally I was able to move my project to Kotlin 2.0.
During the migration from Mockative to Mokkery I had various issues and couldn't fix 3 of them. This issue is targeting to one of it.
If you check the linked PR, I have commented out one test case. This test case was working fine with Mockative, I had to comment it during Mokkery migration since it was giving the below error:
java.util.NoSuchElementException at MainViewModelTest.kt:105
You can see the full log in the link I shared below.
Let me know if anything is needed from my end!
Log: https://github.com/Oztechan/CCC/actions/runs/9614855462/job/26520671149?pr=3579
PR: Oztechan/CCC#3579
Hello,
Thank you for this amazing mocking library, thanks to it I get rid of ksp and decreased my build times, also finally I was able to move my project to Kotlin 2.0.
During the migration from Mockative to Mokkery I had various issues and couldn't fix 3 of them. This issue is targeting to one of it.
If you check the linked PR, I have added 3 test classes back(These test classes were working fine with Mockative), I had to remove these classes during Mokkery migration since it was giving the below error:
e: Compilation failed: Generation of stubs for class org.jetbrains.kotlin.ir.symbols.impl.IrTypeParameterPublicSymbolImpl is not supported yet
* Source files:
* Compiler version: 2.0.0
* Output kind: PROGRAM
e: kotlin.NotImplementedError: Generation of stubs for class org.jetbrains.kotlin.ir.symbols.impl.IrTypeParameterPublicSymbolImpl is not supported yet
at org.jetbrains.kotlin.backend.common.linkage.partial.MissingDeclarationStubGenerator.getDeclaration(MissingDeclarationStubGenerator.kt:62)
at org.jetbrains.kotlin.backend.common.linkage.partial.ClassifierExplorer.exploreSymbol(ClassifierExplorer.kt:96)
at org.jetbrains.kotlin.backend.common.linkage.partial.ClassifierExplorer.exploreType(ClassifierExplorer.kt:75)
at org.jetbrains.kotlin.backend.common.linkage.partial.ClassifierExplorer.exploreType(ClassifierExplorer.kt:65)
at org.jetbrains.kotlin.backend.common.linkage.partial.PartiallyLinkedIrTreePatcher.explore(PartiallyLinkedIrTreePatcher.kt:946)
at org.jetbrains.kotlin.backend.common.linkage.partial.PartiallyLinkedIrTreePatcher.toPartiallyLinkedMarkerTypeOrNull(PartiallyLinkedIrTreePatcher.kt:949)
at org.jetbrains.kotlin.backend.common.linkage.partial.PartiallyLinkedIrTreePatcher.access$toPartiallyLinkedMarkerTypeOrNull(PartiallyLinkedIrTreePatcher.kt:43)
at org.jetbrains.kotlin.backend.common.linkage.partial.PartiallyLinkedIrTreePatcher$DeclarationTransformer.rewriteTypesInFunction(PartiallyLinkedIrTreePatcher.kt:387)
at org.jetbrains.kotlin.backend.common.linkage.partial.PartiallyLinkedIrTreePatcher$DeclarationTransformer.visitSimpleFunction(PartiallyLinkedIrTreePatcher.kt:265)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitSimpleFunction(IrElementTransformerVoid.kt:131)
at org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid.visitSimpleFunction(IrElementTransformerVoid.kt:19)
You can see the full log in the link I shared below.
Let me know if anything is needed from my end!
Log: https://github.com/Oztechan/CCC/actions/runs/9615001222/job/26521152675?pr=3583
PR: Oztechan/CCC#3583
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.