I need to supply shared secret attribute to XML, so I decided to add it without exposing it to my API.
#JacksonXmlRootElement(localName = "Request")
data class TestRequest(#JacksonXmlText val request: String)
Here is example POJO, after serializer it looks like
<Request>text</Request>
I need to add attribute to it, like
<Request secret="foobar">text</Request>
I looked to Jackson API and it looks like I need to create custom serializer for root, so
class SessionModule: SimpleModule("Test serializer", PackageVersion.VERSION) {
override fun setupModule(context: SetupContext) {
context.addBeanSerializerModifier(object : XmlBeanSerializerModifier() {
override fun modifySerializer(config: SerializationConfig, beanDesc: BeanDescription, serializer: JsonSerializer<*>): JsonSerializer<*> {
val modifiedSerializer = super.modifySerializer(config, beanDesc, serializer)
if (modifiedSerializer is XmlBeanSerializer) {
println("Registering custom serializer")
return SessionFieldSerializer(modifiedSerializer)
}
return modifiedSerializer
}
})
}
}
And my custom serializer that do nothing
class SessionFieldSerializer: XmlBeanSerializer {
constructor(src: BeanSerializerBase?) : super(src)
constructor(src: XmlBeanSerializerBase?, objectIdWriter: ObjectIdWriter?, filterId: Any?) : super(src, objectIdWriter, filterId)
constructor(src: XmlBeanSerializerBase?, objectIdWriter: ObjectIdWriter?) : super(src, objectIdWriter)
constructor(src: XmlBeanSerializerBase?, toIgnore: MutableSet<String>?) : super(src, toIgnore)
override fun serialize(bean: Any?, g: JsonGenerator?, provider: SerializerProvider?) {
TODO()
}
}
So, all it do is throw not-implemented exception, however even if SessionFieldSerializer() getting instantiated ( I see "Registering custom serializer" message), serialize function is not called.
Test code:
val mapper = XmlMapper()
mapper.registerModule(KotlinModule())
mapper.registerModule(SessionModule())
val request = TestRequest("Foobar")
val test = mapper.writeValueAsString(request)
println(test)
Am I missing something?
Related
This question already has an answer here:
kotlinx deserialization: different types && scalar && arrays
(1 answer)
Closed 7 months ago.
With a structure similar to the following:
#Serializable
sealed class Parameters
#Serializable
data class StringContainer(val value: String): Parameters()
#Serializable
data class IntContainer(val value: Int): Parameters()
#Serializable
data class MapContainer(val value: Map<String, Parameters>): Parameters()
// more such as list, bool and other fairly (in the context) straight forward types
And the following container class:
#Serializable
data class PluginConfiguration(
// other value
val parameters: Parameters.MapContainer,
)
I want to reach a (de)serialization where the paramters are configured as a flexible json (or other) map, as one would usually expect:
{
"parameters": {
"key1": "String value",
"key2": 12,
"key3": {}
}
}
And so on. Effectively creating a flexible structure that is still structured enough to not be completely uncontrolled as Any would be. There's a fairly clearly defined (de)serialization, but I cannot figure how to do this.
I've tried reading the following
https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serialization-guide.md
And I do have a hunch that a polymorphic serializer is needed, but so far I'm bumping in to either generic structures, which I believe is way overkill for my purpose or that it for some reason cannot find the serializer for my subclasses, when writing a custom serializer for Parameters.
Update
So using custom serializers combined with surrogate classes, most things are working. The current problem is when values are put into the map, I get a kotlin.IllegalStateException: Primitives cannot be serialized polymorphically with 'type' parameter. You can use 'JsonBuilder.useArrayPolymorphism' instead. Even when I enable array polymorphism this error arises
The answer with kotlinx deserialization: different types && scalar && arrays is basically the answer, and the one I will accept. However, for future use, the complete code to my solution is as follows:
Class hierarchy
#kotlinx.serialization.Serializable(with = ParametersSerializer::class)
sealed interface Parameters
#kotlinx.serialization.Serializable(with = IntContainerSerializer::class)
data class IntContainer(
val value: Int
) : Parameters
#kotlinx.serialization.Serializable(with = StringContainerSerializer::class)
data class StringContainer(
val value: String
) : Parameters
#kotlinx.serialization.Serializable(with = MapContainerSerializer::class)
data class MapContainer(
val value: Map<String, Parameters>
) : Parameters
#kotlinx.serialization.Serializable
data class PluginConfiguration(
val plugin: String,
val parameters: MenuRunnerTest.MapContainer
)
Serializers:
abstract class BaseParametersSerializer<T : Parameters> : KSerializer<T> {
override val descriptor: SerialDescriptor = JsonElement.serializer().descriptor
override fun serialize(encoder: Encoder, value: T) {
fun toJsonElement(value: Parameters): JsonElement = when (value) {
is IntContainer -> JsonPrimitive(value.value)
is MapContainer -> JsonObject(
value.value.mapValues { toJsonElement(it.value) }
)
is StringContainer -> JsonPrimitive(value.value)
}
val sur = toJsonElement(value)
encoder.encodeSerializableValue(JsonElement.serializer(), sur)
}
override fun deserialize(decoder: Decoder): T {
with(decoder as JsonDecoder) {
val jsonElement = decodeJsonElement()
return deserializeJson(jsonElement)
}
}
abstract fun deserializeJson(jsonElement: JsonElement): T
}
object ParametersSerializer : BaseParametersSerializer<Parameters>() {
override fun deserializeJson(jsonElement: JsonElement): Parameters {
return when(jsonElement) {
is JsonPrimitive -> when {
jsonElement.isString -> StringContainerSerializer.deserializeJson(jsonElement)
else -> IntContainerSerializer.deserializeJson(jsonElement)
}
is JsonObject -> MapContainerSerializer.deserializeJson(jsonElement)
else -> throw IllegalArgumentException("Only ints, strings and strings are allowed here")
}
}
}
object StringContainerSerializer : BaseParametersSerializer<StringContainer>() {
override fun deserializeJson(jsonElement: JsonElement): StringContainer {
return when(jsonElement) {
is JsonPrimitive -> StringContainer(jsonElement.content)
else -> throw IllegalArgumentException("Only strings are allowed here")
}
}
}
object IntContainerSerializer : BaseParametersSerializer<IntContainer>() {
override fun deserializeJson(jsonElement: JsonElement): IntContainer {
return when (jsonElement) {
is JsonPrimitive -> IntContainer(jsonElement.int)
else -> throw IllegalArgumentException("Only ints are allowed here")
}
}
}
object MapContainerSerializer : BaseParametersSerializer<MapContainer>() {
override fun deserializeJson(jsonElement: JsonElement): MapContainer {
return when (jsonElement) {
is JsonObject -> MapContainer(jsonElement.mapValues { ParametersSerializer.deserializeJson(it.value) })
else -> throw IllegalArgumentException("Only maps are allowed here")
}
}
}
This structure should be expandable for lists, doubles and other structures, not included in the example :)
I'm trying to build a class where certain values are Observable but also Serializable.
This obviously works and the serialization works, but it's very boilerplate-heavy having to add a setter for every single field and manually having to call change(...) inside each setter:
interface Observable {
fun change(message: String) {
println("changing $message")
}
}
#Serializable
class BlahVO : Observable {
var value2: String = ""
set(value) {
field = value
change("value2")
}
fun toJson(): String {
return Json.encodeToString(serializer(), this)
}
}
println(BlahVO().apply { value2 = "test2" })
correctly outputs
changing value2
{"value2":"test2"}
I've tried introducing Delegates:
interface Observable {
fun change(message: String) {
println("changing $message")
}
#Suppress("ClassName")
class default<T>(defaultValue: T) {
private var value: T = defaultValue
operator fun getValue(observable: Observable, property: KProperty<*>): T {
return value
}
operator fun setValue(observable: Observable, property: KProperty<*>, value: T) {
this.value = value
observable.change(property.name)
}
}
}
#Serializable
class BlahVO : Observable {
var value1: String by Observable.default("value1")
fun toJson(): String {
return Json.encodeToString(serializer(), this)
}
}
println(BlahVO().apply { value1 = "test1" }) correctly triggers change detection, but it doesn't serialize:
changing value1
{}
If I go from Observable to ReadWriteProperty,
interface Observable {
fun change(message: String) {
println("changing $message")
}
fun <T> look(defaultValue: T): ReadWriteProperty<Observable, T> {
return OP(defaultValue, this)
}
class OP<T>(defaultValue: T, val observable: Observable) : ObservableProperty<T>(defaultValue) {
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
super.setValue(thisRef, property, value)
observable.change("blah!")
}
}
}
#Serializable
class BlahVO : Observable {
var value3: String by this.look("value3")
fun toJson(): String {
return Json.encodeToString(serializer(), this)
}
}
the result is the same:
changing blah!
{}
Similarly for Delegates.vetoable
var value4: String by Delegates.vetoable("value4", {
property: KProperty<*>, oldstring: String, newString: String ->
this.change(property.name)
true
})
outputs:
changing value4
{}
Delegates just doesn't seem to work with Kotlin Serialization
What other options are there to observe a property's changes without breaking its serialization that will also work on other platforms (KotlinJS, KotlinJVM, Android, ...)?
Serialization and Deserialization of Kotlin Delegates is not supported by kotlinx.serialization as of now.
There is an open issue #1578 on GitHub regarding this feature.
According to the issue you can create an intermediate data-transfer object, which gets serialized instead of the original object. Also you could write a custom serializer to support the serialization of Kotlin Delegates, which seems to be even more boilerplate, then writing custom getters and setters, as proposed in the question.
Data Transfer Object
By mapping your original object to a simple data transfer object without delegates, you can utilize the default serialization mechanisms.
This also has the nice side effect to cleanse your data model classes from framework specific annotations, such as #Serializable.
class DataModel {
var observedProperty: String by Delegates.observable("initial") { property, before, after ->
println("""Hey, I changed "${property.name}" from "$before" to "$after"!""")
}
fun toJson(): String {
return Json.encodeToString(serializer(), this.toDto())
}
}
fun DataModel.toDto() = DataTransferObject(observedProperty)
#Serializable
class DataTransferObject(val observedProperty: String)
fun main() {
val data = DataModel()
println(data.toJson())
data.observedProperty = "changed"
println(data.toJson())
}
This yields the following result:
{"observedProperty":"initial"}
Hey, I changed "observedProperty" from "initial" to "changed"!
{"observedProperty":"changed"}
Custom data type
If changing the data type is an option, you could write a wrapping class which gets (de)serialized transparently. Something along the lines of the following might work.
#Serializable
class ClassWithMonitoredString(val monitoredProperty: MonitoredString) {
fun toJson(): String {
return Json.encodeToString(serializer(), this)
}
}
fun main() {
val monitoredString = obs("obsDefault") { before, after ->
println("""I changed from "$before" to "$after"!""")
}
val data = ClassWithMonitoredString(monitoredString)
println(data.toJson())
data.monitoredProperty.value = "obsChanged"
println(data.toJson())
}
Which yields the following result:
{"monitoredProperty":"obsDefault"}
I changed from "obsDefault" to "obsChanged"!
{"monitoredProperty":"obsChanged"}
You however lose information about which property changed, as you don't have easy access to the field name. Also you have to change your data structures, as mentioned above and might not be desirable or even possible. In addition, this work only for Strings for now, even though one might make it more generic though.
Also, this requires a lot of boilerplate to start with. On the call site however, you just have to wrap the actual value in an call to obs.
I used the following boilerplate to get it to work.
typealias OnChange = (before: String, after: String) -> Unit
#Serializable(with = MonitoredStringSerializer::class)
class MonitoredString(initialValue: String, var onChange: OnChange?) {
var value: String = initialValue
set(value) {
onChange?.invoke(field, value)
field = value
}
}
fun obs(value: String, onChange: OnChange? = null) = MonitoredString(value, onChange)
object MonitoredStringSerializer : KSerializer<MonitoredString> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("MonitoredString", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: MonitoredString) {
encoder.encodeString(value.value)
}
override fun deserialize(decoder: Decoder): MonitoredString {
return MonitoredString(decoder.decodeString(), null)
}
}
Im new to Kotlin and investigating what is/isnt possible
I have a use case as follows:-
As a technical exercise I am attempting to model remote API requests and responses, and enforce relationships between them
My goal is to be able to declare the relationship between Requests and Responses in a clear and succinct way at the top of a Class. This will 1). document the API calls made by this Class, 2). Enforce the relationship so that Request1 can only produce Response1
Pseudo code:-
Requests {
Request1 -> Response1
Request2 -> Response2
...
RequestN -> ResponseN
}
I have defined two interfaces Request & Response and employ them as follows:-
interface Request {
fun <T> response(data : T): Lazy<Response>
}
interface Response
data class Request1(val request: String) : Request {
data class Response1(val output: String) : Response
override fun <T> response(data: T): Lazy<Response> {
return lazy { Response1(data as String) }
}
}
data class Request2(val request: Long) : Request {
data class Response2(val output: Double) : Response
override fun <T> response(data: T): Lazy<Response> {
return lazy { Response2(data as Double) }
}
}
I have a Controller class that makes the API calls as follows:-
class Controller {
fun call(request: Request): Lazy<Response> {
return when (request) {
is Request1 -> request.response("Testing Data")
is Request2 -> request.response(Math.PI)
else -> TODO()
}
}
}
Using the above data classes I can enforce that Request1 is linked to only Response1 and also specify the response data type wrapped by each Response.
Although the above classes provide the functionality and adhere to these rules, they are verbose.
Is there a more succinct approach I could employ to obtain the desired result.
The reason I require this is I am looking for "Self Documenting" code, where a developer can view the definition of Request/Response pairs and association rules and clearly see what is intended.
For example: A developer looking at the final Request definitions can clearly see that Response1 with be generated by Request1. I also want to enforce that Response1 can only ever be produced from Request1.
My example above is simplified, as in "The Real World" the data wrapped by each Response will be sourced from the actual API request call, I have illustrated with "Hard Coded".
I would much rather define Request1 and Response1 on a single line if possible.
UPDATE
I have refactored my original classes as follows:-
interface Request<ResponseData> {
fun response(data: ResponseData): Lazy<Response>
}
interface Response
sealed class Requests<T> : Request<T> {
data class Request1(val request: String) : Requests<String>() {
inner class Response1(val output: String) : Response
override fun response(data: String): Lazy<Response> {
return lazy { Response1(data) }
}
}
data class Request2(val request: Long) : Requests<Double>() {
inner class Response2(val output: Double) : Response
override fun response(data: Double): Lazy<Response> {
return lazy { Response2(data) }
}
}
}
class Controller {
fun <T> call(request: Request<T>): Lazy<Response> {
return when (request) {
is Requests.Request1 -> request.response("Testing Data")
is Requests.Request2 -> request.response(Math.PI)
else -> TODO()
}
}
}
While this version of my code has many benefits from the original, one feature I am still not happy with is that each Request/Response declaration is still quite verbose, e.g. it requires 5 lines of code. Is there an approach I can employ to make each Request/Response pair declaration more succinct?, e.g. take up fewer lines of code.
UPDATE II
Im attempting to refactor my sealed class above so that the overridden function response is defined in the outer sealed class.
interface Request<ResponseData> {
fun response(data: ResponseData): Lazy<Response>
}
interface Response
sealed class Requests<T> : Request<T> {
data class Request1(val request: String) : Requests<String>() {
inner class Response1(val output: String) : Response
}
data class Request2(val request: Long) : Requests<Double>() {
inner class Response2(val output: Double) : Response
}
override fun response(data: T): Lazy<Response> {
return lazy { // What implementation goes here??? // }
}
}
Is this approach possible?
How do I refer to the individual concrete ResponseN classes in the outer sealed class?
Another approach:
data class Box<T, V>(val req: T, val rsp: V)
interface Interaction<RequestT, ResponseT> {
val req: RequestT
fun exec(): Box<RequestT, ResponseT>
}
sealed class Interactions<RequestT, ResponseT> : Interaction<RequestT, ResponseT> {
class Interaction1(override val req: String) : Interaction<String, String> {
override fun exec() = Box(req, "by")
}
class Interaction2(override val req: Long) : Interaction<Long, Double> {
override fun exec() = Box(req, 1.0)
}
}
fun main() {
val interaction1 = Interactions.Interaction1("hi")
val interaction2 = Interactions.Interaction2(42)
println(interaction1.exec()) // Box(req=hi, rsp=by)
println(interaction2.exec()) // Box(req=42, rsp=1.0)
}
Maybe your example is simplified from what you're actually doing, but I don't see the purpose of the Response interface, or the need for separate Request implementations to achieve what your code does:
data class Request<T>(val request: String, val responseType: KClass<out T>) {
fun response(data : T) = lazy { data }
}
class Controller {
fun <T: Any> call(request: Request<T>): Lazy<T> {
#Suppress("UNCHECKED_CAST")
return when (request.responseType) {
String::class -> request.response("Testing Data" as T)
Double::class -> request.response(Math.PI as T)
else -> TODO()
}
}
}
It's kind of an odd use of Lazy though, since you are wrapping a pre-computed value.
My goal is to be able to declare the relationship between Requests and Responses in a clear and succinct way at the top of a Class. This will 1). document the API calls made by this Class, 2). Enforce the relationship so that Request1 can only produce Response1
A great way to enforce the relationships is to separate the interface and implementation levels. Currently you have your interface defined as
interface Request {
fun <T> response(data : T): Lazy<Response>
}
And it does not tell you that the response can vary. It's high level and then you define actual relations in your implementation.
I suggest to decouple relations and the implementation by moving the relations to the interface level.
Here is my suggestion. Forgive me if something does not compile, I'm writing the code from my head, I want to communicate the design ideas and you may have to change some pseudocode.
Let's start with the interface:
interface Response
interface Request // I see that you are using primitive types for requests, so you don't need the interface. But in a real world scenario your requests will probably be more complex than primitive types and then it will make sense to wrap them in this interface. It also makes the code easier to understand - a string can be anything, while a Request is definitely a request.
// This is an interface that actually performs a request, so makes sense to name it in an actionable way
interface Requester<T, M> {
fun <in T: Request, out M: Response> request(data : T): Lazy<M>
}
This declaration tells you that there are different kinds of requests and responses and that there are some relations, but do not say what relations are yet.
Then I would declare the responses and requests implementations in a separate place to keep this code short and to the point
class Request1(val input: String) : Request
class Request2(val input: Double) : Request
class Response1(val output: String) : Response
class Response2(val output: Double) : Response
Then you declare the actual relations
interface Requester1: Requester<Request1, Response1>
interface Requester2: Requester<Request2, Response2>
At this point you have a file that clearly communicates the relation without any implementation details.
This is you final interface code, that solves your request for 1). document the API calls made by this Class, 2). Enforce the relationship so that Request1 can only produce Response1 ⬇️
interface Response
interface Request
interface Requester {
fun <in T: Request, out M: Response> request(data : T): Lazy<M>
}
interface Requester1: Requester<Request1, Response1>
interface Requester2: Requester<Request2, Response2>
Then you can do the implementation in a separate place to keep the interface clean and easy to understand.
sealed class Requests {
data class RequesterImpl1(val request: String) : Requests, Requester1 {
override fun request(data: Request1): Lazy<Response1> {
return lazy { Response1(data) }
}
}
data class RequesterImpl2(val request: Long) : Requests, Requester2 {
override fun request(data: Double2): Lazy<Response2> {
return lazy { Response2(data) }
}
}
}
This is the current design I am using
fun doNothing(): Unit = Unit
interface Interaction<Input, Output> {
interface Response<Output> : Interaction<Unit, Output> {
val output: Output
}
interface Request<Input, Output> : Interaction<Input, Output> {
val input: Input
fun react(output: Output): Response<Output>
}
}
sealed class Interactions<I, O> : Interaction<I, O> {
data class RequestOne(override val input: String) : Interaction.Request<String, Long> {
internal data class ResponseOne(override val output: Long) : Interaction.Response<Long>
override fun react(output: Long): Interaction.Response<Long> = ResponseOne(output)
}
data class RequestTwo(override val input: CustomInput) : Interaction.Request<CustomInput, CustomOutput> {
internal data class ResponseTwo(override val output: CustomOutput) : Interaction.Response<CustomOutput>
override fun react(output: CustomOutput): Interaction.Response<CustomOutput> = ResponseTwo(output)
}
data class RequestThree(override val input: Unit = doNothing()) : Interaction.Request<Unit, CustomOutputTwo> {
internal data class ResponseThree(override val output: CustomOutputTwo) : Interaction.Response<CustomOutputTwo>
override fun react(output: CustomOutputTwo): Interaction.Response<CustomOutputTwo> = ResponseThree(output)
}
data class RequestFour(override val input: Unit = doNothing()) : Interaction.Request<Unit, Unit> {
internal data class ResponseFour(override val output: Unit = doNothing()) : Interaction.Response<Unit>
override fun react(output: Unit): Interaction.Response<Unit> = ResponseFour()
}
data class RequestFive(override val input: CustomInputTwo) : Interaction.Request<CustomInputTwo, Unit> {
internal data class ResponseFive(override val output: Unit = doNothing()) : Interaction.Response<Unit>
override fun react(output: Unit): Interaction.Response<Unit> = ResponseFive()
}
}
I believe this approach enforces the relationships I require between individual Requests and their associated Response types.
The features of this design I would like to improve on is the use of Unit when defining the Response interface.
Also I cannot see a way to improve on the sealed class Interactions<I, O> : Interaction<I, O> {...}, as I never use the Generic I & O
I would also like to be able to define a single fun react(output: Output): Response<Output> within the parent sealed class Interactions instead of having to implement this function in each inner Action data class, however I do not think that is possible.
I have this situation:
abstract class BaseWebClient(baseUrl: String) {
abstract val defaultHeaders: HttpHeaders
val client = WebClient
.builder()
.baseUrl(baseUrl)
.defaultHeaders { it.addAll(defaultHeaders) }
.build()
}
class AnimalsPortalClient(val config: Config, baseUrl: String) : BaseWebClient(baseUrl) {
override val defaultHeaders: HttpHeaders
get() {
val headers = HttpHeaders()
headers.add("app-name", config.appName)
headers.add("app-version", config.appVersion)
return headers
}
fun getAnimals(): String {
return client.get( // ... etc
}
}
This solution doesn't work, because - when defaultHeaders are attempted to be retrieved from overriding property in derived class - the variable config is null.
A possible solution is to pass the config object to the base class' constructor:
abstract class BaseWebClient(val config: Config, baseUrl: String) {
abstract val defaultHeaders: HttpHeaders
// ... etc
}
class AnimalsPortalClient(localConfig: Config, baseUrl: String) : BaseWebClient(localConfig, baseUrl) {
override val defaultHeaders: HttpHeaders
get() {
val headers = HttpHeaders()
headers.add("app-name", config.appName)
headers.add("app-version", config.appVersion)
return headers
}
// ... etc
}
But this solution has a drawback: not all extending classes need a config object. In most of derived class I have empty default headers. Like this:
class SoccerPortalClient(baseUrl: String) : BaseWebClient(baseUrl) {
override val defaultHeaders: HttpHeaders
get() = HttpHeaders()
Using the solution I proposed, I would be forced to always have a config object to pass to the base class, even if there is no need for it.
So basically:
I'm a bit puzzled about the behavior: why the variable is null? Is it a matter of visibility, or...?
What's the correct implementation to get around this problem?
Thank you!
The problem is that your client field in the base class is initialized before anything else. You can initialize it lazily. Like so:
val client by lazy {
WebClient
.builder()
.baseUrl(baseUrl)
.defaultHeaders { it.addAll(defaultHeaders) }
.build()
}
You are trying to access defaultHeaders property at the moment when derived class is not yet initialized.
Consided either convert client property initializer to getter:
val client
get() = WebClient
.builder()
.baseUrl(baseUrl)
.defaultHeaders { it.addAll(defaultHeaders) }
.build()
or use lazy delegate:
val client by lazy {
WebClient
.builder()
.baseUrl(baseUrl)
.defaultHeaders { it.addAll(defaultHeaders) }
.build()
}
Note - I'm a Java+Spring guy trying out Kotlin+Micronaut.
I'm trying to use the TestPropertyProvider to set properties after my embedded service starts.
It works ok, as long as there are no constructor parameters in my test class.
I can add the RxHttpClient as a constructor parameter and it gets injected fine.
But, I'd like to inject the RxHttpClient from Micronaut and also implement TestPropertyProvider.
I tried adding #Inject to the RxHttpClient but get the error This annotation is not applicable to target 'local variable' [because the test body is a lambda passed to the superclass]
Without the #Inject I get the error lateinit property client has not been initialized
My base class has the TestPropertyProvider implementation .
abstract class ZeebeSpecification(body: AbstractStringSpec.() -> Unit): StringSpec(body), TestPropertyProvider {
override fun getProperties(): MutableMap<String, String> {
return mutableMapOf("orchestrator.management.client.brokerContactPoint" to IntegrationTestHarness.instance.getBroker())
}
}
TestPropertyProvider works, but RxHttpClient not injected
#MicronautTest
class ZeebeBroker1Test() : ZeebeSpecification({
#Client("/") lateinit var client: RxHttpClient;
...tests
}) {}
RxHttpClient injected, but TestPropertyProvider not evaluated
#MicronautTest
class ZeebeBroker1Test(#Client("/" val client: RxHttpClient) : ZeebeSpecification({
...tests
}) {}
I removed the base class from the equation and made my test directly implement the TestPropertyProvider but it still fails.
#MicronautTest
class ZeebeBroker1Test(#Client("/") var client: HttpClient) : BehaviorSpec(), TestPropertyProvider {
init {
...tests
}
private fun getBroker(): String {
return IntegrationTestHarness.instance.getBroker()
}
override fun getProperties(): MutableMap<String, String> {
return mutableMapOf("orchestrator.management.client.brokerContactPoint" to getBroker())
}
}
Seems like it's the same issue as this, but I'm already using v1.1.2
https://github.com/micronaut-projects/micronaut-test/issues/82
If I tried to use #Inject #Client("/") client: RxHttpClient it would throw the error message: Missing bean argument [LoadBalancer loadBalancer] for type: io.micronaut.http.client.DefaultHttpClient. Required arguments: LoadBalancer
How do I use both TestPropertyProvider and injected RxHttpClient?
I resolved the issue by moving the body of the spec into the init, and injecting the RxHttpClient as a field.
#MicronautTest
class ZeebeBroker1Test() : ZeebeSpecification() {
#Inject #field:Client("/") lateinit var client: RxHttpClient
private val log: Logger = LoggerFactory.getLogger(ZeebeBroker1Test::class.java)
init {
"test case 1" {
...
}
"test case 2" {
...
}
}
}
And I let the base class implement the TestPropertyProvider interface .
abstract class ZeebeSpecification(): StringSpec(), TestPropertyProvider {
open fun getBroker(): String {
return IntegrationTestHarness.instance.getBroker()
}
override fun getProperties(): MutableMap<String, String> {
return mutableMapOf("orchestrator.management.client.brokerContactPoint" to getBroker())
}
}