KotlinPoet - Generate Koin module - kotlin

I'm new to KotlinPoet and I cannot find how to create the following Koin module statement:
internal val apiModules = module {
single<Name1> { get<Retrofit>().create(Name1::class.java) }
single<Name2> { get<Retrofit>().create(Name2::class.java) }
}
directly into a Kotlin file (no wrapper class)
I have been playing around with PropertySpec and CodeBlock but I don't know how to import Koin DSL or how to reference those imported classes in the code generation. I was also unable to generate the code by pure string generation.

You need to generate the file using FileSpec and add a PropertySpec for the module
It shold look similar to this
val moduleClassName = ClassName("org.koin.core.module.Module", "Module") //This will take care of the import
val moduleMemberName = MemberName("org.koin.dsl.module", "module") //This will take care of the import
val moduleInitilizerCodeBlock =
CodeBlock.Builder()
.beginControlFlow("%M", moduleMemberName) //This will take care of the {} and indentations
.addStatment(ADD ANOTHER CODE BLOCK SIMNILAR TO THIS FOR THE SINGLE/FACTORY)
.endControlFlow()
.build()
val module = PropertySpec.builder("YOUR MODULE NAME", moduleClassName)
.initializer(moduleInitilizerCodeBlock)
.build()
FileSpec.Builder("FILE PACKAGE", "FILE NAME")
.addProperty(module)
.build()
This is not full code but it should point you in the right direction.
Side note: I might me wrong about specific namings but again it should be enough

Related

Kotlin Annotation processor doesn't add import for generated files

I have an Annotation-processor, which should generate a class MyGeneratedClass containing a variable of another class MyEntity.
My code inside the processfunction:
val elementsWithAnnotation = roundEnv.getElementsAnnotatedWith(MyClass::class.java)
if (elementsWithAnnotation.isEmpty()) {
return true
}
val fileName = "MyGeneratedClass"
val packageName = "me.myname.sdk.generated"
val classBuilder = TypeSpec.classBuilder(fileName)
for (element in elementsWithAnnotation) {
val ann = element.getAnnotation(MyClass::class.java)
println("package: "+ ann.javaClass.packageName)
val variableBuilder =
PropertySpec.varBuilder(
name = element.simpleName.toString(),
type = ClassName("", element.asType().asTypeName().asNullable().toString()),
).initializer("null")
classBuilder
.addProperty(variableBuilder.build())
}
val file = FileSpec.builder(packageName, fileName)
.addType(classBuilder.build())
.build()
val generatedDirectory = processingEnv.options[KAPT_KOTLIN_GENERATED_OPTION_NAME]
file.writeTo(File(generatedDirectory, "$fileName.kt"))
return true
But the generated code misses the import MyEntity
package me.myname.sdk.generated
class MyGeneratedClass {
var MyEntity: MyEntity? = null
}
When looking inside the generated file, IntelliJ suggests me to import MyEntity, which resolves the error. But how can I achieve, that the import MyEntity statement is being added when generating the file?
looking at the kotlinpoet documentation https://square.github.io/kotlinpoet/1.x/kotlinpoet/kotlinpoet/com.squareup.kotlinpoet/-class-name/index.html
seems like the first argument in your code, which is a empty string is the package name you are missing in the generated code.
in my experience kotlinpoet is much happier to generate code that in in packages. it sometimes does silly things with types in the root/default package.

kotest change environment variables

I am writing tests for Ktor app using Kotests, but stumbled into the problem how can I change env variables for tests preferably globally. I have tried adding withEnvironment but it throw quite strange error into me
Unable to make field private final java.util.Map java.util.Collections$UnmodifiableMap.m accessible: module java.base does not "opens java.util" to unnamed module #3daa422a
java.lang.reflect.InaccessibleObjectException: Unable to make field private final java.util.Map java.util.Collections$UnmodifiableMap.m accessible: module java.base does not "opens java.util" to unnamed module #3daa422a
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
my test file looks like this
class VisitorSpec : FreeSpec({
val ds = createDataSourceTest()
val visitor = RegisterVisitorDTO(
email = TestConstants.VISITOR_EMAIL,
username = TestConstants.VISITOR_USERNAME,
password = TestConstants.PASSWORD,
firstName = TestConstants.VISITOR_FIRST_NAME,
lastName = TestConstants.VISITOR_LAST_NAME,
gender = TestConstants.VISITOR_GENDER,
birthday = TestConstants.VISITOR_BIRTHDAY,
)
"check visitor routes" - {
val loginData = LoginDTO(TestConstants.VISITOR_EMAIL + 0, TestConstants.PASSWORD)
"can get list of visitors with correct query" {
withEnvironment(
mapOf(
"POSTGRES_URL" to "jdbc:postgresql://localhost:5432/test",
"POSTGRES_USERNAME" to "test_user",
"POSTGRES_PASSWORD" to "test_pass"
)
) {
testApplication {
val client = getClient(ds)
repeat(6) {
registerConfirmedUser(
client, visitor.copy(
email = "${TestConstants.VISITOR_EMAIL}$it",
username = "${TestConstants.VISITOR_USERNAME}$it",
)
)
}
val accessToken = loginUser(client, loginData).run { this.body<LoginResponseDTO>().accessToken }
client.get("/api/v1/visitors?page=1&count=5") {
header("Authorization", "Bearer $accessToken")
}.apply {
val response = this.body<VisitorPaginatedResponseDTO>()
response.data.size.shouldBe(5)
response.totalCount.shouldBe(6)
response.currentPage.shouldBe(1)
}
}
}
}
...
if I remove
withEnvironment(
mapOf(
"POSTGRES_URL" to "jdbc:postgresql://localhost:5432/test",
"POSTGRES_USERNAME" to "test_user",
"POSTGRES_PASSWORD" to "test_pass"
)
)
it will just work but with default db, any advice on this?
In some places, it was advised to use
override fun listeners() = listOf(
SystemEnvironmentTestListener("fooKeyEnv", "barValueEnv"),
SystemPropertyTestListener("fooKeyProp", "barValueProp")
)
but ide tells me that this method is deprecated.
Thanks in advance for any advice.
Recent Java versions prohibit modifying the environment variables with the default access settings (JEP 403: Strongly Encapsulate JDK Internals). Kotest and some other testing frameworks that manipulate the environment variables got affected by this, you can find the related issues:
https://github.com/kotest/kotest/issues/2849
https://github.com/stefanbirkner/system-lambda/issues/23
https://github.com/junit-pioneer/junit-pioneer/issues/509
One solution would be to add the arguments to the JVM running the tests that would make the Java Platform Module System allow the access to the API used by the test framework. Here's an answer that explains the arguments: How to set environment variable in Java without 'illegal reflective access'? How to use add-opens?
The simplest form of the argument, if you are not using Java modules in your code, would be:
--add-opens java.base/java.util=ALL-UNNAMED
If you are running the tests using Gradle, then you can pass this argument to the jvmArgs of the test task:
tasks.withType<Test>().named("jvmTest") {
jvmArgs("--add-opens", "java.base/java.util=ALL-UNNAMED")
}
Note: modifying the module access in this way could make the tests pass even if some of your code needs illegal access to the JDK internals. Make sure that your code doesn't do that or that you have other tests that check for this without modifying module access rights.
It seems that some other libraries, like system-stubs, provide a way to modify the environment variables in tests without illegal reflective access to the JDK internals.

How do you mock a URL connection in Kotlin?

I've seen a lot of examples on how to mock a connection in Java but haven't seen any explaining how to do it in Kotlin. A bit of code that I want mocked as an example:
val url = URL("https://google.ca")
val conn = url.openConnection() as HttpURLConnection
with(conn) {
//doStuff
}
conn.disconnect()
Similar to a question like this but for Kotlin:
how to mock a URL connection
Kotlin and Java can interop with one another, so you should be able to take your exact example (from the question) provided and convert it to Kotlin (or don't convert it and call the Java directly):
#Throws(Exception::class)
fun function() {
val r = RuleEngineUtil()
val u = PowerMockito.mock(URL::class.java)
val url = "http://www.sdsgle.com"
PowerMockito.whenNew(URL::class.java).withArguments(url).thenReturn(u)
val huc = PowerMockito.mock(HttpURLConnection::class.java)
PowerMockito.`when`(u.openConnection()).thenReturn(huc)
PowerMockito.`when`(huc.getResponseCode()).thenReturn(200)
assertTrue(r.isUrlAccessible(url))
}
It's worth noting that you should probably consider using an actual mocking HTTP server like HttpMocker for handling this as opposed to implement the behavior yourself.

XDocReport converting odt to pdf how to set proper locale

I'm trying to convert some *.odt file to *.pdf using IXDocReport.
Here is the hypothetical content of the *.odt file: ${amount?string.currency} to be paid
Here is the code I do conversion with (you can run it in kotlin REPL):
import fr.opensagres.xdocreport.converter.ConverterTypeTo
import fr.opensagres.xdocreport.converter.ConverterTypeVia
import fr.opensagres.xdocreport.converter.Options
import fr.opensagres.xdocreport.document.IXDocReport
import fr.opensagres.xdocreport.document.registry.XDocReportRegistry
import fr.opensagres.xdocreport.template.TemplateEngineKind
import java.io.ByteArrayInputStream
import java.io.File
val options: Options = Options.getTo(ConverterTypeTo.PDF).via(ConverterTypeVia.ODFDOM)
val content: ByteArray = File("/home/sandro/tmp/report.odt").readBytes()
val templateId: String = "someId"
val registry: XDocReportRegistry = XDocReportRegistry.getRegistry()
val data: MutableMap<String, Any> = mutableMapOf("amount" to 10)
ByteArrayInputStream(content).use { input ->
val report: IXDocReport =
registry.loadReport(input, templateId, TemplateEngineKind.Freemarker, true)
val tmpFile: File = createTempFile("out", ".pdf")
tmpFile.outputStream().use { output ->
report.convert(data, options, output)
println(tmpFile.toString())
}
}
and the result is the pdf file with string $10.00 to be paid
How can I set needed locale to XDocReport during conversion so the result could be changed to other currencies correctly?
P.S. I cannot control the template itself - so please do not tell me to add <#setting locale="${bean.locale}"> or something else to the template itself. The only place I can change is the code. Thanks in advance.
P.P.S. I need to render many templates per request and need to set locale per each template.
I have never used XDocReport, but maybe this will work: https://github.com/opensagres/xdocreport/wiki/FreemarkerTemplate "How to configure Freemarker?"
Quotation from there:
To configure Freemarker with XDocReport you must get the Configuration instance. To do > that you must
create a class (ex :
fr.opensagres.xdocreport.MyFreemarkerConfiguration) which implements
fr.opensagres.xdocreport.document.discovery.ITemplateEngineInitializerDiscovery.
register with SPI this class by creating the file
META-INF/services/fr.opensagres.xdocreport.document.discovery.ITemplateEngineInitializerDiscovery
with the name of you class :
fr.opensagres.xdocreport.MyFreemarkerConfiguration This file should be
in your classpath (you can for instance host it in the
src/META-INF/services/ of your project).
So you will need a class like this:
public class MyFreemarkerConfiguration implements ITemplateEngineInitializerDiscovery {
[...]
public void initialize(ITemplateEngine templateEngine) {
if (TemplateEngineKind.Freemarker.name().equals( templateEngine.getKind())) {
Configuration cfg = ((FreemarkerTemplateEngine) templateEngine).getFreemarkerConfiguration();
cfg.setLocale(...);
}
}
}

How to mock the 'new' operator

I'm testing some groovy code that uses a java library and I want to mock out the library calls because they use the network. So the code under test looks something like:
def verifyInformation(String information) {
def request = new OusideLibraryRequest().compose(information)
new OutsideLibraryClient().verify(request)
}
I tried using MockFor and StubFor but I get errors such as:
No signature of method: com.myproject.OutsideLibraryTests.MockFor() is applicable for argument types: (java.lang.Class) values: [class com.otherCompany.OusideLibraryRequest]
I'm using Grails 2.0.3.
I've just found that we can always overwrite a constructor via MetaClass, as Grails 2 will be reset MetaClass modification at the end of each test.
This trick is better than Groovy's MockFor. AFAIK, Groovy's MockFor does not allow us to mock JDK's classes, java.io.File, for example. However in the below example, you cannot use File file = new File("aaa") as the real object type is a Map, not a File. The example is a Spock specification.
def "test mock"() {
setup:
def fileControl = mockFor(File)
File.metaClass.constructor = { String name -> [name: name] }
def file = new File("aaaa")
expect:
file.name == "aaaa"
}
The second, optional parameter to MockFor's constructor is interceptConstruction. If you set this to true, you can mock the constructor. Example:
import groovy.mock.interceptor.MockFor
class SomeClass {
def prop
SomeClass() {
prop = "real"
}
}
def mock = new MockFor(SomeClass, true)
mock.demand.with {
SomeClass() { new Expando([prop: "fake"]) }
}
mock.use {
def mockedSomeClass = new SomeClass()
assert mockedSomeClass.prop == "fake"
}
Note, however, you can only mock out groovy objects like this. If you're stuck with a Java library, you can pull the construction of the Java object into a factory method and mock that.