Kotlin Micronaut Mockk no answer found exception - kotlin

I am new to the Micronaut framework and trying to test a demo example I created.
I am encountering the error io.mockk.MockKException: no answer found for: MathFacade(#3).computeAgain(3).
My code works as such - MathService invokes MathFacade. I want to test MathService so I mock MathFacade in my MathServiceTest with intentions to only test MathService.
My code is as below:
MathServiceTest
package io.micronaut.test.kotlintest
import io.kotlintest.specs.BehaviorSpec
import io.micronaut.test.annotation.MicronautTest
import io.micronaut.test.annotation.MockBean
import io.micronaut.test.extensions.kotlintest.MicronautKotlinTestExtension.getMock
import io.mockk.mockk
import io.mockk.every
import io.reactivex.Single
import kotlin.math.pow
import kotlin.math.roundToInt
#MicronautTest
class MathServiceTest(
private val mathService: MathService,
private val mathFacade: MathFacade
): BehaviorSpec({
given("the math service") {
`when`("the service is called with 3") {
val mock = getMock(mathFacade)
every { mock.computeAgain(any()) } answers {
Single.just(firstArg<Int>().toDouble().pow(3).roundToInt())
}
val result = mathService.compute(2)
then("the result is 9") {
mathService.compute(3).test().assertResult(27)
}
}
}
}) {
#MockBean(MathFacade::class)
fun mathFacade(): MathFacade {
return mockk()
}
}
MathService
package io.micronaut.test.kotlintest
import io.reactivex.Single
import javax.inject.Singleton
#Singleton
class MathService(private val mathFacade: MathFacade) {
fun compute(num: Int): Single<Int> {
return mathFacade.computeAgain(num)
}
}
MathFacade
package io.micronaut.test.kotlintest
import io.reactivex.Single
import javax.inject.Singleton
#Singleton
class MathFacade() {
fun computeAgain(num: Int): Single<Int>{
return Single.just(num*4)
}
}
Any help will be appreciated!

Related

Error while accessing references from Resource.kt file in Kotlin project

I get below error while trying to access refferences from my Resources.kt file
.
Below shows my full code on LoginViewModel.kt
package lk.ac.kln.mit.stu.mobileapplicationdevelopment.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.bumptech.glide.load.engine.Resource
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseUser
import com.google.firebase.firestore.auth.User
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
#HiltViewModel
class LoginViewModel #Inject constructor(
private val firebaseAuth: FirebaseAuth
): ViewModel() {
private val _login = MutableSharedFlow<Resource<FirebaseUser>>()
// MutableSharedFlow<Resource<FirebaseUser>>()
val login = _login.asSharedFlow()
fun login(email: String, password: String){
viewModelScope.launch {
_login.emit(Resource.Loading())
}
firebaseAuth.signInWithEmailAndPassword(
email,password
).addOnSuccessListener {
viewModelScope.launch {
it.user?.let {
_login.emit(Resource.Success(it))
}
}
}.addOnFailureListener {
viewModelScope.launch {
_login.emit(Resource.Error(it.message.toString()))
}
}
}
}
Below shows the Resource.kt file
package lk.ac.kln.mit.stu.mobileapplicationdevelopment.util
sealed class Resource<T>(
val data: T? = null,
val message: String? = null
){
class Success<T>(data:T):Resource<T>(data)
class Error<T>(message:String):Resource<T>(message = message)
class Loading <T>: Resource<T>()
class Unspecified <T>: Resource<T>()
}
I have tried to implement using the same way I did for other view models.
I did the same approach to access same variables on RegisterViewModel.kt and it works without any error. Below is the code for that functioning view model
package lk.ac.kln.mit.stu.mobileapplicationdevelopment.viewmodel
import androidx.lifecycle.ViewModel
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseUser
import com.google.firebase.firestore.FirebaseFirestore
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.runBlocking
import lk.ac.kln.mit.stu.mobileapplicationdevelopment.data.User
import lk.ac.kln.mit.stu.mobileapplicationdevelopment.util.*
import lk.ac.kln.mit.stu.mobileapplicationdevelopment.util.Constants.USER_COLLECTION
import javax.inject.Inject
#HiltViewModel
class RegisterViewModel #Inject constructor(
private val firebaseAuth : FirebaseAuth,
private val db: FirebaseFirestore
) : ViewModel() {
private val _register = MutableStateFlow<Resource<User>>(Resource.Unspecified())
val register : Flow<Resource<User>> = _register
private val _validation = Channel<RegisterFieldsState>()
val validation =_validation.receiveAsFlow()
fun createAccountWithEmailAndPassword(user: User, password: String) {
if (checkValidation(user, password)){
runBlocking {
_register.emit(Resource.Loading())
}
firebaseAuth.createUserWithEmailAndPassword(user.email, password)
.addOnSuccessListener {
it.user?.let {
saveUserInfo(it.uid, user)
// _register.value = Resource.Success(it)
}
}
.addOnFailureListener {
_register.value = Resource.Error(it.message.toString())
}
} else
{
val registerFieldsState = RegisterFieldsState(
validateEmail(user.email), validatePassword(password)
)
runBlocking {
_validation.send(registerFieldsState)
}
}
}
private fun saveUserInfo(userUid: String, user: User) {
db.collection(USER_COLLECTION)
.document(userUid)
.set(user)
.addOnSuccessListener {
_register.value = Resource.Success(user)
}
.addOnFailureListener {
_register.value = Resource.Error(it.message.toString())
}
}
private fun checkValidation(
user: User,
password: String
) : Boolean {
val emailValidation = validateEmail(user.email)
val passwordValidation = validatePassword(password)
val shouldRegister =
emailValidation is RegisterValidation.Success && passwordValidation is RegisterValidation.Success
return shouldRegister
}
}
Can someone help me with solving above issue please.Why it gives an error when trying to call Resource.Loading() from LoginViewModel.kt?
Please check import class in LoginViewModel, You are importing
import com.bumptech.glide.load.engine.Resource
It should be from this package lk.ac.kln.mit.stu.mobileapplicationdevelopment.util

Using async func in Compose Web

I should use Ktor client in Compose Web. But, It can't be called in Compose Web due to async/non-async problem.
Environment is template project made by IntelliJ IDEA.
First, I use this:
val client=HttpClient(Js){
install(ContentNegotiation){
json()
}
}
suspend fun shorterCall(url:String):String{
val response = client.get(SERVER_NAME) {
contentType(ContentType.Application.Json)
parameter("url", url)
}
return response.bodyAsText()
}
suspend fun main() {
var i by mutableStateOf("")
renderComposable(rootElementId = "root") {
Div({ style {
height(100.vh)
margin(0.px)
width(100.vw)
} }) {
Input(type = InputType.Url) {
onInput {
val input=it.value.trim()
if(input.startsWith("http"))
i=shorterCall(input)
else
i="NaN"
}
}
Text(i)
}
}
}
Then, I got that error:
Suspend function can be called only within a coroutine body.
So, I tried another one:
import kotlinx.coroutines.*
fun shorterCall(url:String):String{
var ret:String
suspend fun t():String {
val response = client.get(SERVER_NAME) {
contentType(ContentType.Application.Json)
parameter("url", url)
}
return response.bodyAsText()
}
runBlocking{
ret=t()
}
return ret
}
//main func is same as upper one.
Then, I got that error:
Unresolved reference: runBlocking
+editing body 1: When I use GlobalScope.launch or js("JSCode"), It raise that error:
e: Could not find "kotlin" in [/home/user/.local/share/kotlin/daemon]
e: java.lang.IllegalStateException: FATAL ERROR: Could not find "kotlin" in [/home/user/.local/share/kotlin/daemon]
(a lot of internal errors bellow)
You can use the GlobalScope.launch() method to launch a job for a request in a browser environment:
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import io.ktor.client.*
import io.ktor.client.engine.js.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.jetbrains.compose.web.attributes.InputType
import org.jetbrains.compose.web.css.*
import org.jetbrains.compose.web.dom.*
import org.jetbrains.compose.web.renderComposable
fun main() {
val client = HttpClient(Js) {}
var i by mutableStateOf("")
renderComposable(rootElementId = "root") {
Div({
style {
height(100.vh)
margin(0.px)
width(100.vw)
}
}) {
Input(type = InputType.Url) {
onInput {
val input = it.value.trim()
if (input.startsWith("http")) {
GlobalScope.launch {
i = client.shorterCall(input)
}
} else {
i = "NaN"
}
}
}
Text(i)
}
}
}
suspend fun HttpClient.shorterCall(url: String): String {
val response = get(url) {
contentType(ContentType.Application.Json)
}
return response.bodyAsText()
}

TransactionalEventListener not invoked in #MicronautTest

Problem description
While system end-to-end tests are invoking methods annotated with #TransactionalEventListener, I'm not able to invoke the same methods in narrower tests annotated with #MicronautTest.
I've tested numerous variants with both injected EntityManager and SessionFactory. #MicronautTest(transactional = false) is also tested. Calling JPA-method inside TestSvcWithTxMethod#someMethod is also tested with same result. I've also tried tests without mocking TestAppEventListener.
The below test/code yields
Verification failed: call 1 of 1:
TestAppEventListener(#1).beforeCommit(any())) was not called.
java.lang.AssertionError: Verification failed: call 1 of 1:
TestAppEventListener(#1).beforeCommit(any())) was not called.
Calls to same mock: 1) TestAppEventListener(#1).hashCode()
Environment: Micronaut 3.7.5, Micronaut Data 3.9.3
Minimal reproducible code
Test is failing as well with transactional = false
import io.kotest.core.spec.style.BehaviorSpec
import io.micronaut.test.annotation.MockBean
import io.micronaut.test.extensions.kotest5.MicronautKotest5Extension.getMock
import io.micronaut.test.extensions.kotest5.annotation.MicronautTest
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import no.mycompany.myapp.eventstore.services.appeventpublisher.testinfra.DefaultTestAppEventListener
import no.mycompany.myapp.eventstore.services.appeventpublisher.testinfra.TestAppEventListener
import no.mycompany.myapp.eventstore.services.appeventpublisher.testinfra.TestSvcWrapper
#MicronautTest
class AppEventWithBeforeCommitListenerMockTest(
testSvcWrapper: TestSvcWrapper,
testAppEventListener: TestAppEventListener
) : BehaviorSpec({
given("context with app event listener") {
`when`("calling someMethod") {
val mockBeforeCommitTestListener = getMock(testAppEventListener)
every { mockBeforeCommitTestListener.beforeCommit(any()) } answers {}
every { mockBeforeCommitTestListener.afterRollback(any()) } answers {}
testSvcWrapper.someMethod(message = "call #1")
verify { mockBeforeCommitTestListener.beforeCommit(any()) }
}
}
}) {
#MockBean(DefaultTestAppEventListener::class)
fun mockTestAppEventListener(): TestAppEventListener = mockk()
}
TestSvcWrapper
import jakarta.inject.Singleton
#Singleton
class TestSvcWrapper(
private val testSvcWithTxMethod: TestSvcWithTxMethod
) {
fun someMethod(message: String) {
testSvcWithTxMethod.someMethod(message)
}
}
TestSvcWithTxMethod
import io.micronaut.context.event.ApplicationEventPublisher
import jakarta.inject.Singleton
import javax.transaction.Transactional
#Singleton
open class TestSvcWithTxMethod(
private val eventPublisher: ApplicationEventPublisher<TestEvent>
) {
#Transactional(Transactional.TxType.REQUIRES_NEW)
open fun someMethod(message: String) {
eventPublisher.publishEvent(TestEvent(message))
}
}
TestEvent
import io.micronaut.core.annotation.Introspected
#Introspected
data class TestEvent(val message: String)
TestAppEventListener
interface TestAppEventListener {
fun beforeCommit(event: TestEvent)
fun afterRollback(event: TestEvent)
}
DefaultTestAppEventListener
import io.micronaut.transaction.annotation.TransactionalEventListener
import jakarta.inject.Singleton
import java.util.concurrent.atomic.AtomicInteger
#Singleton
open class DefaultTestAppEventListener : TestAppEventListener {
val receiveCount = AtomicInteger()
#TransactionalEventListener(TransactionalEventListener.TransactionPhase.BEFORE_COMMIT)
override fun beforeCommit(event: TestEvent) {
receiveCount.getAndIncrement()
}
#TransactionalEventListener(TransactionalEventListener.TransactionPhase.AFTER_ROLLBACK)
override fun afterRollback(event: TestEvent) {
receiveCount.getAndIncrement()
}
}
The answer was found in the micronaut-test repo. Key is to inject SynchronousTransactionManager<Any>, create and then commit/rollback transaction.
I was not able to make mock-test from question pass, most likely because of the annotations, but the following tests are working. I made some modifications to the types in question, hence I added code for the new implementations below.
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe
import io.micronaut.test.extensions.kotest5.annotation.MicronautTest
import io.micronaut.transaction.SynchronousTransactionManager
import io.micronaut.transaction.support.DefaultTransactionDefinition
import no.mycompany.myapp.eventstore.services.appeventpublisher.testinfra.TestAppEventListener
import no.mycompany.myapp.eventstore.services.appeventpublisher.testinfra.TestSvcWithTxMethod
#MicronautTest(transactional = false)
class AppEventWithBeforeCommitListenerTest(
testSvcWithTxMethod: TestSvcWithTxMethod,
testAppEventListener: TestAppEventListener,
transactionManager: SynchronousTransactionManager<Any>
) : BehaviorSpec({
given("context with app event listener") {
`when`("calling someMethod with commit") {
val tx = transactionManager.getTransaction(DefaultTransactionDefinition())
testSvcWithTxMethod.someMethod(message = "call #1")
transactionManager.commit(tx)
then("TestAppEventListener should have received message") {
testAppEventListener.beforeCommitReceiveCount.get() shouldBe 1
}
}
`when`("calling someMethod with rollback") {
val tx = transactionManager.getTransaction(DefaultTransactionDefinition())
testSvcWithTxMethod.someMethod(message = "call #2")
transactionManager.rollback(tx)
then("TestAppEventListener should have received message") {
testAppEventListener.afterRollbackReceiveCount.get() shouldBe 1
}
}
}
})
TestSvcWithTxMethod
import io.micronaut.context.event.ApplicationEventPublisher
import jakarta.inject.Singleton
import javax.transaction.Transactional
#Singleton
open class TestSvcWithTxMethod(
private val eventPublisher: ApplicationEventPublisher<TestEvent>
) {
#Transactional
open fun someMethod(message: String) {
eventPublisher.publishEvent(TestEvent(message))
}
}
TestAppEventListener
import io.micronaut.transaction.annotation.TransactionalEventListener
import jakarta.inject.Singleton
import java.util.concurrent.atomic.AtomicInteger
#Singleton
open class TestAppEventListener {
val beforeCommitReceiveCount = AtomicInteger()
val afterRollbackReceiveCount = AtomicInteger()
#TransactionalEventListener(TransactionalEventListener.TransactionPhase.BEFORE_COMMIT)
open fun beforeCommit(event: TestEvent) {
beforeCommitReceiveCount.getAndIncrement()
}
#TransactionalEventListener(TransactionalEventListener.TransactionPhase.AFTER_ROLLBACK)
open fun afterRollback(event: TestEvent) {
afterRollbackReceiveCount.getAndIncrement()
}
}
TestEvent (unchanged)
import io.micronaut.core.annotation.Introspected
#Introspected
data class TestEvent(val message: String)

How do I start the application only once when testing in Ktor, instead of once per test?

I've been trying to write some tests for my Ktor application, and have followed the docs here:
https://ktor.io/docs/testing.html#end-to-end
...and using a test setup like this:
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.testing.*
import kotlin.test.*
class ApplicationTest {
#Test
fun testRoot() = testApplication {
val response = client.get("/")
assertEquals(HttpStatusCode.OK, response.status)
assertEquals("Hello, world!", response.bodyAsText())
}
}
The problem is that when using testApplication in every test, is that the tests crashes when I have around 220 tests that should be run, because my application reads a json-file for every boot - resulting in a "too many open files" error.
What I want to do is run the application once, then send all my HTTP-requests to this single instance of the application, and then close the application.
What is instead happening above is that the application is booted and closed for each one of the over 200 tests, resulting in memory errors.
How do I run the application only once?
Solved it!
We can start the application in a beforeAll function, instead of starting it in every test. Here's an example of how to do that:
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.server.testing.*
import io.ktor.test.dispatcher.*
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
class MyApiTest {
lateinit var JSON: Json
#Test
fun `Test some endpoint`() = testSuspend {
testApp.client.get("/something").apply {
val actual = JSON.decodeFromString<SomeType>(bodyAsText())
assertEquals(expected, actual)
}
}
companion object {
lateinit var testApp: TestApplication
#JvmStatic
#BeforeAll
fun setup() {
testApp = TestApplication { }
}
#JvmStatic
#AfterAll
fun teardown() {
testApp.stop()
}
}
}

kotlin testing error cannot create parent directories

When I run test, I am getting the error when I try to test if Datastore.edit had been called:
java.io.IOException: Unable to create parent directories of /data/user/0/com.example.app.test/files/datastore/user.preferences_pb
It is a small class that handles Datastore, which is the following:
Pref.kt
class Pref {
companion object {
#Volatile
private var INSTANCE: Pref? = null
private var dataStore: DataStore<Preferences>? = null
fun getInstance(context: Context): Pref? {
if (INSTANCE == null) {
synchronized(Pref::class) {
INSTANCE = Pref()
dataStore = context.createDataStore("user")
}
}
return INSTANCE
}
}
suspend fun save(key: String, value: String) {
val dataStoreKey = preferencesKey<String>(key)
dataStore?.edit { settings ->
settings[dataStoreKey] = value
}
}
}
PrefTest.kt
import android.content.Context
import android.provider.ContactsContract
import android.util.Log
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.datastore.core.DataStore
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.*
import org.junit.Rule
import org.junit.runner.RunWith
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.preferencesKey
import androidx.test.platform.app.InstrumentationRegistry
import io.mockk.*
import io.mockk.MockKAnnotations.init
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
#RunWith(AndroidJUnit4::class)
class PrefTest {
private val context : Context = InstrumentationRegistry.getInstrumentation().context
#get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
private val mockDataStore = mockk<DataStore<Preferences>>()
#Before
fun setup() {
init(this, relaxed = true)
}
#Test
fun saveValue() = runBlocking {
val dataStoreKey = preferencesKey<String>(storeKey)
Pref.getInstance(context)?.save("key", "value")
coVerify { mockDataStore.edit {
dataStoreKey
} }
}
}
in my device, I can see the directory /data/user/0/com.example.app.test/files. But not sure why the datastore directories couldn't be created for it to continue testing.