Quarkus + kotlin dependency injection in tests - kotlin

I am working on my quarkus + kotlin application. Having class:
#ApplicationScoped
class ParrallerCronScheduler(private val someService: SomeService) : CronScheduler
When the application is running there is no problem, quarkus injects someService.
Running this tests class:
#QuarkusTest
#QuarkusTestResource(PostgresResource::class)
internal class CroneUpdateTest(private val someService: SomeService): BaseIntegrationTest()
makes quarkus throw:
When using constructor injection in a test, the only legal operation is to assign the constructor values to fields. Offending class is class com.tui.promotion_campaigns_service.services.impl.CroneUpdateTes
which forces to use this imo ugly thing in tests:
#Inject
lateinit var someService: SomeService
Why the first way of test implementation not working? Is it possible to make it work?
I tried
class ParrallerCronScheduler #Inject constructor(private val someService: SomeService) : CronScheduler

Related

Hilt Singleton doesn't seems to work in my Service

I'm facing an issue, like if my repository injected was not a Singleton.
I have a repository (in reality many, but let's make it simple) marked as #Singleton
#Module
#InstallIn(SingletonComponent::class)
class AppModule {
#Provides
#Singleton
fun provideSettingsRepository(#ApplicationContext context: Context): SettingsRepository {
return SettingsRepositoryImpl(context)
}
}
Here the implementation of my repository :
class SettingsRepositoryImpl(context: Context) : SettingsRepository {
private var _flow = MutableStateFlow("init value")
override fun getFlow(): StateFlow<String?> = _flow.asStateFlow()
override fun setMyValue(value:String) {
_flow.value = value
}
}
When I use it apart of my service (in some viewModels or others class with DI), it work perfectly. Today I implemented an AIDL service and wanted to do some DI. I had to use field injection because the service constructor has to be empty. It seems like the update made from my application isen't reported on my "TestApplication" who consume the Service (like if I had two instance of my repository).
Here the code of my service :
#AndroidEntryPoint
class AppService : Service() {
#Inject lateinit var settingsRepository: SettingsRepository
fun someActionOnMyRepository() {
settingsRepository.setMyValue("whatever")
}
}
When I set the value from my UI (viewModel or any other class who as the repository injected), it's not updated in my service. The flow doesn't contains the new value (tested by debug or logcat).
I'm expecting my settingsRepository to be a singleton. What am I missing ? Is it because the field injection ?
Best regards
Ok, the problem was not from Hilt but about how i declared my service in my AndroidManisfest.xml
<service android:name=".services.MyAppService"
android:process=":remote" <----- THIS
android:exported="true">
Making it like that make it on another process. That mean it's like another app instance (so no more Singleton / SharedPreferences).

Guice & Dropwizard - Field Injection not working - while Constructor Injection does

I have a test class that looks something like this
Behind the scenes, we're using Guice and DropwizardAwareModule to configure the binding and provide instantiated classes/beans.
class SomeTest {
companion object {
#RegisterExtension
#JvmField
val app = TestGuiceyAppExtension.forApp(MyServer::class.java)
.config(configFileName).hooks(GuiceyConfigurationHook {
builder(it)
}).create()
}
#Inject
#Named("something")
private lateinit var someClass: SomeClass
}
The SomeClass is defined in the following way:
class SomeClass(firstClass: FirstClass, secondClass: SecondClass) {
}
With no constructor injection.
In a separate DropwizardAwareModule, I define a Provides function that provides 2 possible instances of SomeClass. For example -
#Provides
#Named("something")
fun getSomeClass(firstClass: FirstClass, secondClass: SecondClass) {
return SomeClass(firstClass, secondClass)
}
The test class fails to inject SomeClass, claiming it doesn't have a no-args constructor, nor an #Inject annotated constructor, fair enough.
However, if I transform the field injection into a constructor injection, it works flawlessly.
That is - this configuration works well, and the SomeClass instance I provided in the module above, shows up alright.
class SomeTest #Inject constructor(#Named("something") someClass: SomeClass) {
}
Why is it so? is it because of some limitation in test classes? or is it something to do with #Provides generating an immutable instance, whereas field injection requires mutable objects?
Thanks!

How to create a TestContainers base test class in Kotlin with JUnit 5

I am trying to use Neo4j TestContainers with Kotlin, Spring Data Neo4j, Spring Boot and JUnit 5. I have a lot of tests that require to use the test container. Ideally, I would like to avoid copying the container definition and configuration in each test class.
Currently I have something like:
#Testcontainers
#DataNeo4jTest
#Import(Neo4jConfiguration::class, Neo4jTestConfiguration::class)
class ContainerTest(#Autowired private val repository: XYZRepository) {
companion object {
const val IMAGE_NAME = "neo4j"
const val TAG_NAME = "3.5.5"
#Container
#JvmStatic
val databaseServer: KtNeo4jContainer = KtNeo4jContainer("$IMAGE_NAME:$TAG_NAME")
.withoutAuthentication()
}
#TestConfiguration
internal class Config {
#Bean
fun configuration(): Configuration = Configuration.Builder()
.uri(databaseServer.getBoltUrl())
.build()
}
#Test
#DisplayName("Create xyz")
fun testCreateXYZ() {
// ...
}
}
class KtNeo4jContainer(val imageName: String) : Neo4jContainer<KtNeo4jContainer>(imageName)
How can I extract the databaseServer definition and the #TestConfiguration? I tried different ways of creating a base class and having the ContainerTest extend it, but it is not working. From what I understand, static attriubutes are not inherited in Kotlin.
Below my solution for sharing same container between tests.
#Testcontainers
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
abstract class IntegrationTest {
companion object {
#JvmStatic
private val mongoDBContainer = MongoDBContainer(DockerImageName.parse("mongo:4.0.10"))
.waitingFor(HostPortWaitStrategy())
#BeforeAll
#JvmStatic
fun beforeAll() {
mongoDBContainer.start()
}
#JvmStatic
#DynamicPropertySource
fun registerDynamicProperties(registry: DynamicPropertyRegistry) {
registry.add("spring.data.mongodb.host", mongoDBContainer::getHost)
registry.add("spring.data.mongodb.port", mongoDBContainer::getFirstMappedPort)
}
}
}
The key here is to not use #Container annotation as it will close just created container after your first test subclass executes all tests.
Method start() in beforeAll() initialize container only once (upon first subclass test execution), then does nothing while container is running.
By theory we shouldn't have to do this hack, based on:
https://www.testcontainers.org/test_framework_integration/junit_5/
...container that is static should not be closed until all of tests of all subclasses are finished, but it's not working that way and I don't know why. Would be nice to have some answer on that :).
I've had the same issue (making Spring Boot + Kotlin + Testcontainers work together) and after searching the web for (quite) a while I found this nice solution: https://github.com/larmic/testcontainers-junit5. You'll just have to adopt it to your database.
I faced very similar issue in Kotlin and spring boot 2.4.0.
The way you can reuse one testcontainer configuration can be achieved through initializers, e.g.:
https://dev.to/silaev/the-testcontainers-mongodb-module-and-spring-data-mongodb-in-action-53ng or https://nirajsonawane.github.io/2019/12/25/Testcontainers-With-Spring-Boot-For-Integration-Testing/ (java versions)
I wanted to use also new approach of having dynamicProperties and it worked out of a boxed in java. In Kotlin I made sth like this (I wasn't able to make #Testcontainer annotations working for some reason). It's not very elegant but pretty simple solution that worked for me:
MongoContainerConfig class:
import org.testcontainers.containers.MongoDBContainer
class MongoContainerConfig {
companion object {
#JvmStatic
val mongoDBContainer = MongoDBContainer("mongo:4.4.2")
}
init {
mongoDBContainer.start()
}
}
Test class:
#SpringBootTest(
classes = [MongoContainerConfig::class]
)
internal class SomeTest {
companion object {
#JvmStatic
#DynamicPropertySource
fun setProperties(registry: DynamicPropertyRegistry) {
registry.add("mongodb.uri") {
MongoContainerConfig.mongoDBContainer.replicaSetUrl
}
}
}
Disadvantage is this block with properties in every test class what suggests that maybe approach with initializers is desired here.

Dagger 2 in Kotlin : Is there a way to do injection for class with default parameterized constructor without using Module?

I want to have my Dagger 2 inject the below class and doesn't plan to use #Module to do so.
So I put #Inejct constructor as below
class InjectClass #Inject constructor(var txt: String = "Default")
It doesn't work as it complaints
e: [kapt] An exception occurred: java.lang.IllegalStateException: Found multiple
#Inject constructors: [InjectClass(java.lang.String), InjectClass()]
Is there a way to make it work here?
Instead of using a default value, you could write a secondary constructor instead:
class InjectClass #Inject constructor(var txt: String) {
constructor(): this("Default")
}
Like this you make sure that the default constructor is not annotated with #Inject and Dagger knows how to create InjectClass.
Optionally, we could do this
class InjectClass(var txt: String) {
#Inject constructor(): this("Default")
}

Dagger2 generate multiple instances

I'm using Dagge2 in my kotlin project with pom file. I worked with Dagger in my android projects and it worked fine. But somehow I don't understand why Dagger generating multiple instances of my every object in kotlin.
Below is my Component
#Singleton
#Component(modules = [MeterCollectionModule::class])
interface AppComponent {
fun meterCollection(): MeterCollection
}
And this is my Module
#Module(includes = [UtilModule::class])
class MeterCollectionModule {
#Singleton
#Provides
fun meterCollection() = MeterCollection()
}
That's how I'm building my AppComponent
DaggerAppComponent
.builder()
.build()
.inject(this)
I debug my code and see, every time I inject MeterCollection class it gives me new object.
The #Singleton annotation (as well as any other scope annotation) is taken into account only if you're reusing the same component. In other words, Dagger is not able to respect your #Singleton scope across different instances of the same component.
Hence, in order to inject the same MeterCollection instance, you should also reuse the same DaggerAppComponent instance (e.g., by putting it in an instance variable).
Singleton and Provider annotations won't work with object which is created every time method is called. I'd refactor the class to:
#Module(includes = [UtilModule::class])
class MeterCollectionModule {
val myMeterConnection = MeterConnection()
#Singleton
#Provides
fun meterCollection(){
return myMeterConnection
}
(which is identical solution to what #user2340612 proposed)