Kotlin coroutine in Spring DataJpaTest - kotlin

In a spring Boot service we have a new Kotlin coroutine implementation which works nicely.
However, when trying to test it the JpaRepository seems to get "truncated" when accessed on a coroutine block. here is a simplified portion of the code:
import kotlinx.coroutines.*
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.junit.jupiter.SpringExtension
import javax.persistence.*
#ExtendWith(SpringExtension::class)
#ContextConfiguration(classes = [SpringTestConfig::class])
#DataJpaTest
class BadRepoTest(#Autowired val testedRepository: TestRepository) {
#Test
fun testRepo() {
testedRepository.saveAndFlush(TestEntity())
println("count at start: ${testedRepository.count()} and $testedRepository")
runBlocking {
runCodeAsync().await()
}
println("count post async code: ${testedRepository.count()} and $testedRepository")
}
fun runCodeAsync(): Deferred<Unit> {
return GlobalScope.async {
println("count in async code: ${testedRepository.count()} and $testedRepository")
}
}
}
interface TestRepository : JpaRepository<TestEntity, Int>
#Entity
#Table(name = "test")
data class TestEntity(
#Id #GeneratedValue(strategy = GenerationType.IDENTITY) val id: Int? = null
)
I would expect the repository to contain a single entity at all time during the test run, but the code prints:
count at the start: 1 and SimpleJpaRepository#40d04cf8
count in async code: 0 and SimpleJpaRepository#40d04cf8
count post async code: 1 and SimpleJpaRepository#40d04cf8
Could you please advise?

Related

How do I provide my own DateTimeProvider with Spring Data

I am trying to provide a transactionally consistent set of datetimes
import org.springframework.beans.factory.ObjectFactory
import org.springframework.beans.factory.config.BeanFactoryPostProcessor
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Scope
import org.springframework.data.auditing.DateTimeProvider
import org.springframework.transaction.support.SimpleTransactionScope
import java.time.Instant
import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.util.Optional
#Configuration
open class TransactionScopeTimeConfiguration {
#Bean
open fun transactionBFPP(): BeanFactoryPostProcessor =
BeanFactoryPostProcessor { it.registerScope("transaction", SimpleTransactionScope()) }
#Bean
#Scope("transaction")
open fun nowInstant(): Instant = Instant.now()
#Bean
#Scope("transaction")
open fun nowOffsetDateTime(nowInstant: Instant): OffsetDateTime = nowInstant.atOffset(ZoneOffset.UTC)
#Bean
open fun transactionDateTimeProvider(factory: ObjectFactory<OffsetDateTime>): DateTimeProvider =
DateTimeProvider { Optional.of(factory.`object`) }
}
However, when debugging my test I note that the function inside of transactionDateTimeProvider is never called (the creation of the Bean is)
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
#DataJpaTest
#Import(TransactionScopeTimeConfiguration)
internal open class ExceptionDaoTest {
#Test
fun save(#Autowired exceptionDao: ExceptionJpaDao) {
val toCreate = ExceptionEntity("someid")
val saved = exceptionDao.save(toCreate)
assertThat(saved).isInstanceOf(ExceptionEntity::class.java)
.extracting({ it.id }, { it.businessDivisionId })
.containsExactly(toCreate.id, "someid")
.doesNotContainNull()
assertThat(saved.lastModifiedOn).isSameAs(saved.createdOn)
}
}
The test actually passes, but I haven't actually exercised this functionality in this test. It's important that any other datetimes are transactionally consistent with the audit traits.

How to call method Reference in map

I create a class ApUtils . Define two methods in that class . I want
use that two methods in my service class inside the map function by
the help of method reference ,But I am getting un excepted error while
calling these two methods
service.kt
package com.nilmani.reactivespringdemo.services
import com.nilmani.reactivespringdemo.Utils.AppUtils
import com.nilmani.reactivespringdemo.dto.ProductDto
import com.nilmani.reactivespringdemo.entity.Product
import com.nilmani.reactivespringdemo.repository.ProductRepository
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
#Service
class ProductService {
#Autowired
private lateinit var productRepository: ProductRepository
fun getProduct():Flux<ProductDto>{
return productRepository.findAll().map(obj: AppUtils -> obj.entityToDto(product))
}
fun getProduct(id:String):Mono<ProductDto>{
return productRepository.findById(id).map{ obj: AppUtils, product: Product -> obj.entityToDto(product) }
}
}
AppUtils.kt
package com.nilmani.reactivespringdemo.Utils
import com.nilmani.reactivespringdemo.dto.ProductDto
import com.nilmani.reactivespringdemo.entity.Product
import org.springframework.beans.BeanUtils
class AppUtils {
fun entityToDto(product: Product):ProductDto{
val productDto = ProductDto()
BeanUtils.copyProperties(product,productDto)
return productDto
}
fun dtoToEntity(productDto: ProductDto):Product{
val product = Product()
BeanUtils.copyProperties(productDto,product)
return product
}
}
How to call these two function in my service class using method reference
If you change AppUtils to a singleton using Kotlin's object keyword:
object AppUtils { ... }
then you can use method references like so:
fun getProduct(id:String) = productRepository.findById(id).map(AppUtils::entityToDto)

What is the reason for an UninitializedPropertyAccessException in #SpringBootTest with MockK and Kotlin?

I am trying to use MockK 1.10.2 with Kotlin 1.4.10 and #SpringBootTest (Spring Boot 2.2.2.RELEASE) and I can't get it run because of a
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
2020-11-05 15:00:37.878 WARN --- [ main] i.m.p.j.t.InliningClassTransformer : Failed to transform class java/lang/Object
java.lang.IllegalArgumentException: Unsupported class file major version 59
at net.bytebuddy.jar.asm.ClassReader.<init>(ClassReader.java:195)
at net.bytebuddy.jar.asm.ClassReader.<init>(ClassReader.java:176)
at net.bytebuddy.jar.asm.ClassReader.<init>(ClassReader.java:162)
at net.bytebuddy.utility.OpenedClassReader.of(OpenedClassReader.java:86)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining.create(TypeWriter.java:3394)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default.make(TypeWriter.java:1933)
at net.bytebuddy.dynamic.scaffold.inline.RedefinitionDynamicTypeBuilder.make(RedefinitionDynamicTypeBuilder.java:217)
at net.bytebuddy.dynamic.scaffold.inline.AbstractInliningDynamicTypeBuilder.make(AbstractInliningDynamicTypeBuilder.java:120)
at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase.make(DynamicType.java:3404)
at io.mockk.proxy.jvm.transformation.InliningClassTransformer.transform(InliningClassTransformer.kt:77)
at java.instrument/java.lang.instrument.ClassFileTransformer.transform(ClassFileTransformer.java:246)
at java.instrument/sun.instrument.TransformerManager.transform(TransformerManager.java:188)
at java.instrument/sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:563)
at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:167)
at io.mockk.proxy.jvm.transformation.JvmInlineInstrumentation.retransform(JvmInlineInstrumentation.kt:28)
at io.mockk.proxy.common.transformation.RetransformInlineInstrumnetation$execute$1.invoke(RetransformInlineInstrumnetation.kt:19)
at io.mockk.proxy.common.transformation.RetransformInlineInstrumnetation$execute$1.invoke(RetransformInlineInstrumnetation.kt:6)
at io.mockk.proxy.common.transformation.ClassTransformationSpecMap.applyTransformation(ClassTransformationSpecMap.kt:41)
at io.mockk.proxy.common.transformation.RetransformInlineInstrumnetation.execute(RetransformInlineInstrumnetation.kt:16)
at io.mockk.proxy.jvm.ProxyMaker.inline(ProxyMaker.kt:88)
at io.mockk.proxy.jvm.ProxyMaker.proxy(ProxyMaker.kt:30)
kotlin.UninitializedPropertyAccessException: lateinit property taService has not been initialized
exception. (The same happens if I use tmpService. It doesn't matter if the imported service is in the same or a different package as the test class.)
As you can see from the code of my test class I already played around a lot but without any success so far.
package com.xxx.emr.tm
import com.xxx.data.emr.model.PatientAssignment
import com.xxx.data.tm.TMPService
import com.xxx.data.tm.model.MyType
import com.xxx.data.tm.model.MyTypeCode
import com.xxx.data.tm.model.UserAction
import com.xxx.emr.service.model.AuthenticatedUser
import io.mockk.every
import io.mockk.impl.annotations.InjectMockKs
import io.mockk.impl.annotations.SpyK
import io.mockk.junit5.MockKExtension
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.verify
import org.junit.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
#ExtendWith(SpringExtension::class, MockKExtension::class)
#SpringBootTest // (classes = [com.xxx.emr.service.MyServiceSpringBoot::class])
// #RunWith(SpringJUnit4ClassRunner::class)
// #ActiveProfiles("test")
// #EnableAutoConfiguration
// #AutoConfigureMockMvc
class MyTest {
// (
// val tmpService: TMPService,
// val taService: TaService
// )
// #InjectMockKs
// #Autowired
// #SpyK
#MockK
private lateinit var tmpService: TMPService
#InjectMockKs
#Autowired
private lateinit var taService: TaService
#Test
fun assignAToB() {
MockKAnnotations.init(this, relaxUnitFun = true)
val vId = "xxx"
val authUser: AuthenticatedUser = AuthenticatedUser.mockedUser()
val userAction = UserAction(
userId = "user_id",
actionCode = ActionType.GET_IT,
sessionId = "sessionId"
)
val xxx = MyType(
MyTypeCode.ABC.value,
MyTypeCode.ABC.name,
)
val actionResult = UserActionResult(hashMapOf("success" to true))
// every { tmpService.getXxx(any()) } returns xxx
verify( exactly = 1 ) {
tmpService.doSomething(any(), any(), any())
}
verify( exactly = 1 ) {
taService.dowhatYouWant(„vId", authUser, userAction)
}
}
Autowiring does not work...so what is my fault? How can I make the test run at all?
I think you should initialize your mocks first, by adding this in your test file for example:
#org.springframework.test.context.event.annotation.BeforeTestMethod
fun initMocks() {
org.mockito.MockitoAnnotations.initMocks(this)
}
I created a post on another thread about how to setup unit testing, comparable with your requirements:
https://stackoverflow.com/a/64669499/7919904

#Autowired member not initialized in a Junit 5 test written in Kotlin

I can't get an #Autowired member to be initialized in a JUnit 5 test. Here is the test:
import org.amshove.kluent.`should be equal to`
import org.junit.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean
#SpringBootTest
class SnackQueryResolverTest {
#TestConfiguration
class SnackQueryResolverTestConfig {
#Bean
fun snackQueryResolverFactory() = SnackQueryResolver()
}
#Autowired
private lateinit var snackQueryResolver: SnackQueryResolver
#Test
fun `snacks`() {
val snacks = snackQueryResolver.snacks()
snacks.size `should be equal to` 5
}
}
When the test is run I receive the error:
kotlin.UninitializedPropertyAccessException: lateinit property snackQueryResolver has not been initialized
If I eliminate the #Autowired and instantiate the bean during construction, the test runs fine:
#SpringBootTest
class SnackQueryResolverTest {
private val snackQueryResolver: SnackQueryResolver = SnackQueryResolver()
#Test
fun `snacks`() {
val snacks = snackQueryResolver.snacks()
snacks.size `should be equal to` 5
}
}
What am I missing?
Turns out a small error was causing the issue - careless with IDE code completion. For #Test, I was importing:
import org.junit.Test
instead, this needs to be used for JUnit 5 tests:
import org.junit.jupiter.api.Test

How to pass current ClassLoader to KotlinToJVMBytecodeCompiler for dynamic (runtime) compilation kotlin code programmatically?

I created simple utility for runtime compilation kotlin code:
package com.example
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoot
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.com.intellij.openapi.Disposable
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.JvmTarget
import java.io.File
import kotlin.script.experimental.jvm.util.KotlinJars
class KotlinDynamicCompiler {
fun compileScript(moduleName: String,
sourcePath: String,
saveClassesDir: File
): GenerationState {
val stubDisposable = StubDisposable();
val configuration = CompilerConfiguration()
configuration.put(CommonConfigurationKeys.MODULE_NAME, moduleName)
configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, PrintingMessageCollector(System.out, MessageRenderer.PLAIN_FULL_PATHS, true))
configuration.put(JVMConfigurationKeys.OUTPUT_DIRECTORY, saveClassesDir)
configuration.put(JVMConfigurationKeys.JVM_TARGET, JvmTarget.JVM_1_8)
configuration.addKotlinSourceRoot(sourcePath)
configuration.addJvmClasspathRoots(listOf(KotlinJars.stdlib))
val env = KotlinCoreEnvironment.createForProduction(stubDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
return KotlinToJVMBytecodeCompiler.analyzeAndGenerate(env)!!;
}
inner class StubDisposable : Disposable {
#Volatile
var isDisposed: Boolean = false
private set
override fun dispose() {
isDisposed = true
}
};
}
And it works for code as
package com.example.kt
class SimpleClass(val str:String){
fun test(){
}
}
class UsedSimpleClass(val simpleClass: SimpleClass, val file: java.io.File) {
}
But it not works if I want to use no-base package classes as:
package com.example.kt
import com.example.pojo.TestPojo //class have in project that call runtime compilation
class SimpleClass(val str:TestPojo){
}
or:
package com.example.kt
import com.fasterxml.jackson.databind.ObjectMapper //class have in project classpath where called runtime compilation
class SimpleClass(val str:ObjectMapper){
}
How to pass current ClassLoader to KotlinToJVMBytecodeCompiler for dynamic (runtime) compilation kotlin code programmatically?
More details:
Test project on github with crashed test: https://github.com/nekkiy/dynamic-kotlin
Cause:
We need use codegeneration and would like to test generated code. But I don't understand how to pass current classes environment.
Thanks for attention.
Solution:
I have used method fun classpathFromClassloader(currentClassLoader: ClassLoader, unpackJarCollections: Boolean = false): List<File>? from kotlin.script.experimental.jvm.util.jvmClasspathUtil.kt and it works.
Result dynamic compiller:
package com.example
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoots
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.com.intellij.openapi.Disposable
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.JvmTarget
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.PrintStream
import kotlin.script.experimental.jvm.util.KotlinJars
import kotlin.script.experimental.jvm.util.classpathFromClassloader
class KotlinDynamicCompiler {
fun compileModule(moduleName: String,
sourcePath: List<String>,
saveClassesDir: File,
classLoader: ClassLoader? = null,
forcedAddKotlinStd: Boolean = true
): GenerationState {
val stubDisposable = StubDisposable();
val configuration = CompilerConfiguration()
configuration.put(CommonConfigurationKeys.MODULE_NAME, moduleName)
val baos = ByteArrayOutputStream()
val ps: PrintStream = PrintStream(baos)
configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, PrintingMessageCollector(ps, MessageRenderer.PLAIN_FULL_PATHS, true))
configuration.put(JVMConfigurationKeys.OUTPUT_DIRECTORY, saveClassesDir)
// configuration.put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, true)
configuration.put(JVMConfigurationKeys.JVM_TARGET, JvmTarget.JVM_1_8)
val classPath = mutableSetOf<File>()
if (classLoader != null) {
classPath.addAll(classpathFromClassloader(classLoader)!!);
}
if (forcedAddKotlinStd) {
classPath.add(KotlinJars.stdlib)
}
configuration.addJvmClasspathRoots(classPath.toList())
configuration.addKotlinSourceRoots(sourcePath)
val env = KotlinCoreEnvironment.createForProduction(stubDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
val result = KotlinToJVMBytecodeCompiler.analyzeAndGenerate(env);
ps.flush();
if (result != null) {
return result
} else {
throw IllegalStateException("Compilation error. Details:\n$baos")
}
}
inner class StubDisposable : Disposable {
#Volatile
var isDisposed: Boolean = false
private set
override fun dispose() {
isDisposed = true
}
};
}
Note: This function is contained in experimental package.
P.S. I also updated github-project.