Koin mocking suspend function - testing

Can anybody tell me if/how I can mock a supend funktion with koin test? The only thing I know so far is this behavior
declareMock<...> {
given(..)).willReturn(...)
}
but this doesn't work on suspend fun(). Is there anything similar to 'coEvery' in Mockk, or how can I do this?
Thanx in advance,
Wolfgang

Finally I found out how it works. You just can use any other mocking framework and use declare with that mock like so:
var preferenceRepository = mockk<PreferenceRepository>()
#Before
fun before() {
startKoin {
androidContext(ApplicationProvider.getApplicationContext())
}
declare {
factory { preferenceRepository }
}
}

Related

Co-routines in Kotlin multiplatform project

I'm trying to use "runTest()" in Kotlin multiplatform. I'm using Jetbrains's "Getting started"-project as an example. (https://kotlinlang.org/docs/multiplatform-library.html)
The problem is that runTest() does not find a coroutine context. It gives me the following build error:
Cannot access class 'kotlin.coroutines.CoroutineContext'. Check your module classpath for missing or conflicting dependencies
Here is my test:
class Base64JvmTest {
#OptIn(ExperimentalCoroutinesApi::class)
#Test
fun testNonAsciiString() {
runTest {
val utf8String = "Gödel"
val actual = Base64Factory.createEncoder().encodeToString(utf8String.toByteArray())
assertEquals("R8O2ZGVs", actual)
}
}
}
In build.gradle.kts, I set the following in kotlin.sourceSets:
val jvmTest by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4")
}
}
Please help me out - what am I missing?
As it turns out, there was an issue with Idea. I added the following dependency to get rid of the error:
dependencies {
commonTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4")
}
It shouldn't really be needed, as the common tests are not dependent on coroutines, but an acceptable work-around.
you can run runTest following way as documentation is suggested
#Test
fun exampleTest() = runTest {
val deferred = async {
delay(1_000)
async {
delay(1_000)
}.await()
}
deferred.await() // result available immediately
}
documentation code link

how to use Junit5 #TempDir with Kotlin ? ("JvmField can only be applied to final property" compile error)

I'm trying (without any luck so far) to use Junit5 #Tempdir annotation with Kotlin.
Following a previous stackoverflow post (link here), I've tried the code below:
#SpringBootTest
class MyClass {
#TempDir
#JvmField
var tempFolder: File? = null
#Test
fun mytest() {
assert(true);
}
}
Unfortunately I get the following error at compilation: "JvmField can only be applied to final property"...
Any idea ?
Thanks a lot in advance for your expertise and your time.
Best Regards
For other people still looking for an answer, below code works around above-mentionned issue:
#SpringBootTest
class MyClass {
#Test
fun mytest() {
assert(true);
}
companion object {
#TempDir
lateinit var tempFolder: File
}
}

How to override in Kotiln Volley JsonObjectRequest class?

I'm writing a Kotlin app that has a class. I need that class to extend JsonObjectRequest, since I need to override the function
override fun parseNetworkResponse(response: NetworkResponse?): Response<T>
That's because I need to interpret in Kotlin the HTTP response code the server is sending.
However, I admit to being new to Kotlin and haven't managed to figure out how to extend the JsonObjectRequest class. I keep running into silly compiler issues.
Can someone provide a quick example of that?
After a bit of iteration, i managed to finally figure it out. Posting it here since it may be useful to others -
class DataRequest(
method: Int,
uri: String,
jsonObject: JSONObject,
listener: Response.Listener<JSONObject>,
errorListener: Response.ErrorListener
) :
JsonObjectRequest(method, uri, jsonObject, listener, errorListener)
{
override fun parseNetworkResponse(response: NetworkResponse): Response<JSONObject>
{
try
{
val jsonString = String(
response.data,
Charset.forName(HttpHeaderParser.parseCharset(response.headers))
)
return Response.success(
JSONObject(jsonString), HttpHeaderParser.parseCacheHeaders(response)
)
} catch (e: UnsupportedEncodingException)
{
return Response.error(ParseError(e))
} catch (je: JSONException)
{
return Response.error(ParseError(je))
}
}
}

Kotlin + Mockito + NullPointerException thrown when stubbing

Today I stumbled across a situation which I do not understand, possibly because of lack of insight into how mockito and mockito-kotlin work internally.
Given the code below, from my Kotlin beginner perspective, I have two pretty similar interface-methods. One returns Boolean, one String. Both are suspend functions in my example as in my real world situation my functions are too.
class ITestInterface {
suspend fun returnBoolean(): Boolean {
return true
}
suspend fun returnSomeString() : String {
return "String"
}
}
#Test
fun demoTest() {
val implMock = mock<ITestInterface> {
on {
runBlocking {
returnSomeString()
}
} doReturn "Hello"
on {
runBlocking {
returnBoolean()
}
} doReturn false
}
}
My observation is, when I run the test, like depicted above, I get the following error Message
com.nhaarman.mockitokotlin2.MockitoKotlinException: NullPointerException thrown when stubbing.
This may be due to two reasons:
- The method you're trying to stub threw an NPE: look at the stack trace below;
- You're trying to stub a generic method: try `onGeneric` instead.
at com.nhaarman.mockitokotlin2.KStubbing.on(KStubbing.kt:72)
at com.rewedigital.fulfillment.picking.components.substitute.DemoTest.demoTest(DemoTest.kt:30)
[...]
Experiments showed that
the behavior is only shown by the Boolean returning function, not by returnSomeString()
removing the suspend keyword fro the returnBoolean function makes the error go away
using onGeneric as suggested in the error message also makes the error go away
Could anybody explain why this is happening? To what extend do we have to do with generics here? And what would be the correct way of solving the issue in our real application? Having a bunch of on {} and some onGeneric {} ?
Thanks for your help!
You should use the onBlocking method to properly mock the suspend function
Please try the following code:
#Test
fun demoTest() {
val implMock = mock<ITestInterface> {
onBlocking {
returnSomeString()
} doReturn "Hello"
onBlocking {
returnBoolean()
} doReturn false
}
runBlocking {
// Execute your code here
assertThat(implMock.returnSomeString()).isEqualTo("Hello")
assertThat(implMock.returnBoolean()).isEqualTo(false)
}
}

How do I use custom configuration in Ktor?

I'm digging the built-in configuration support, and want to use it (instead of just rolling my own alongside Ktor's), but I'm having a hard time figuring out how to do it in a clean way. I've got this, and it's working, but it's really ugly and I feel like there has to be a better way:
val myBatisConfig = MyBatisConfig(
environment.config.property("mybatis.url").getString(),
environment.config.property("mybatis.driver").getString(),
environment.config.property("mybatis.poolSize").getString().toInt())
installKoin(listOf(mybatisModule(myBatisConfig), appModule), logger = SLF4JLogger())
Thanks for any help!
Adding to the existing accepted answer. An implementation using ConfigFactory.load() could look like this (Without libs):
object Config {
#KtorExperimentalAPI
val config = HoconApplicationConfig(ConfigFactory.load())
#KtorExperimentalAPI
fun getProperty(key: String): String? = config.propertyOrNull(key)?.getString()
#KtorExperimentalAPI
fun requireProperty(key: String): String = getProperty(key)
?: throw IllegalStateException("Missing property $key")
}
So, theconfig class would become:
val myBatisConfig = MyBatisConfig(
requireProperty("mybatis.url"),
requireProperty("mybatis.driver"),
requireProperty("mybatis.poolSize").toInt())
Okay, I think I have a good, clean way of doing this now. The trick is to not bother going through the framework itself. You can get your entire configuration, as these cool HOCON files, extremely easily:
val config = ConfigFactory.load()
And then you can walk the tree yourself and build your objects, or use a project called config4k which will build your model classes for you. So, my setup above has added more configuration, but gotten much simpler and more maintainable:
installKoin(listOf(
mybatisModule(config.extract("mybatis")),
zendeskModule(config.extract("zendesk")),
appModule),
logger = SLF4JLogger())
Hope someone finds this useful!
You could also try this solution:
class MyService(val url: String)
fun KoinApplication.loadMyKoins(environment: ApplicationEnvironment): KoinApplication {
val myFirstModule = module {
single { MyService(environment.config.property("mybatis.url").getString()) }
}
val mySecondModule = module {}
return modules(listOf(myFirstModule, mySecondModule))
}
fun Application.main() {
install(DefaultHeaders)
install(Koin) {
loadMyKoins(environment)
SLF4JLogger()
}
routing {
val service by inject<MyService>()
get("/") {
call.respond("Hello world! This is my service url: ${service.url}")
}
}
}
fun main(args: Array<String>) {
embeddedServer(Netty, commandLineEnvironment(args)).start()
}