Configuration and dependency injection in a scenario
QALIPSIS relies on the Dependency injection framework from Micronaut and supports the injection of dependencies in the constructors of the scenario declaring classes and methods annotated with @Scenario.
Ensure you have the following dependency in the project where your beans or factories are compiled: kapt("io.qalipsis:qalipsis-api-processors:0.16.x")
|
Injecting a singleton bean
You can declare a singleton bean using the annotation @jakarta.inject.Singleton on a type, or use a Micronaut factory.
This allows to create a single instance of the class that can be injected in the constructors or methods of scenario declaring classes and reused across all the scenarios or in other beans.
This is a powerful feature that allows you to create reusable components, such as services or utilities, that can be shared across multiple scenarios without the need to instantiate them multiple times.
You can even package them in separate libraries and share them across multiple QALIPSIS projects.
Creation and injection of beans
import jakarta.inject.Singleton
@Singleton // The annotation `@jakarta.inject.Singleton` can be added to a class to automatically create a bean.
class CalculationService {
fun calculateSomething(): Int {
// ...
return 42
}
}
@Singleton
class SurpriseService {
fun surpriseSomething(): String {
// ...
return "Tada!"
}
}
// Once beans are created, you can inject them in the constructor or the scenario method:
class MyScenario(
private val calculationService: CalculationService // The bean of type `CalculationService` is injected in the constructor of the scenario declaring class.
) {
@Scenario("calculate-something")
fun doSomething(surpriseService: SurpriseService) {
scenario {
// ...
}.start()
.execute {
// Use the injected bean in the constructor
calculationService.calculateSomething()
}.execute {
// Use the injected bean as a parameter of the scenario method
surpriseService.surpriseSomething()
}
}
}
Qualifying the bean to inject
When several beans exist for the same type, you might want to select the one to inject. QALIPSIS supports named injection as in the following example.
import jakarta.inject.Named
class MyScenario(
@param:Named("default") private val calculationService: CalculationService // The bean of type `CalculationService` is injected in the constructor of the scenario declaring class.
) {
@Scenario("calculate-something")
fun doSomething(@Named("simple") surpriseService: SurpriseService) {
scenario {
// ...
}.start()
.execute {
// Use the injected bean in the constructor
calculationService.calculateSomething()
}.execute {
// Use the injected bean as a parameter of the scenario method
surpriseService.surpriseSomething()
}
}
}
QALIPSIS only supports name qualifiers; but you can read more about other qualifiers in the Micronaut documentation.
Injecting configuration
Properties for the runtime property sources are also injectable in scenarios via parameters of constructors and methods.
import io.qalipsis.api.annotations.Property
class EcommercePurchase {
@Scenario("login-select-and-checkout")
fun fromLoginToCheckout(@Property(name = "ecommerce-purchase.action-pace", orElse = "10s") actionPaceDuration: Duration) { (1)
scenario {
// ...
}
.constantPace(actionPaceDuration.toMillis())
// ...
}
}
The default value set in orElse is optional.
The value of ecommerce-purchase.action-pace can then be specified in the qalipsis.yml file or as a command line argument when starting QALIPSIS.
More information about the configuration is available in the Configuration and execution section.
Injecting iterable containers
QALIPSIS supports the injection of a single instance of beans as shown above, as well as an Iterable or any of its subtypes Collection, List, Set and their derivatives and Optional for properties as in the example below.
class EcommercePurchase(private val retryPolicies: List<RetryPolicy>) {
@Scenario("e-commerce-purchase")
fun fromLoginToCheckout(@Property("startDelay") executor: Optional<Duration>) {
// ...
}
}
Summary
The sample code below includes all the supported methods of injection for beans and properties.
The example is for a scenario method, but can also be used for the constructor of a class declaring scenarios.
@Scenario
fun fromLoginToCheckout(
classToInject: ClassToInject, (1)
mayBeOtherClassToInject: Optional<OtherClassToInject>, (2)
@Named("myInjectable") namedInterfaceToInject: InterfaceToInject, (3)
injectables: List<InterfaceToInject>, (4)
@Named("myInjectable") namedInjectables: List<InterfaceToInject>, (5)
@Property(name = "this-is-a-test") property: Duration, (6)
@Property(name = "this-is-another-test", orElse = "10") propertyWithDefaultValue: Int, (7)
@Property(name = "this-is-yet-another-test") mayBeProperty: Optional<String> (8)
) {
}
<1> An instance of `ClassToInject` is injected, failing if missing.
<2> An instance of `Optional<OtherClassToInject>` is injected, replaced by an empty optional if missing.
<3> An instance of `InterfaceToInject` qualified by the name `myInjectable` is injected, failing if missing.
<4> A `List` of all the instances of classes inheriting from `InterfaceToInject` is injected.
<5> A `List` of all the instances of classes inheriting from `InterfaceToInject` and qualified by the name `myInjectable` is injected.
<6> The value of the property `this-is-a-test` is injected after its conversion to a `Duration`, failing if missing.
<7> The value of the property `this-is-another-test` is injected after its conversion to a `Int`, replace by 10 as Int if missing.
<8> The value of the property `this-is-yet-another-test` is injected after its conversion to a `String`, replaced by an empty optional if missing.