Version: spring-test-5.3.27

TLDR: Why do all web-test Kotlin DSL classes have internal constructors?

I find MockMvcExtensionsKt really helpful and currently replacing all usages of MockMvcRequestBuilders with a Kotlin DSL. What I'm missing and prefer asking before submitting a PR is why all the constructors are internal. I'd like to have the possibility to extend it. Yes, some things could be done using the extensions, e.g. fun ContentResultMatchersDsl.myObject(smth). But it's not possible to do so with a context.

One of the goals I'm trying to achieve is to ease up the creation of the request body. We are only using json. The repetitiveness of content = objectMapper.writeValueAsString(body) is a bit annoying. I want to introduce a wrapper that will be aware of the object mapper. But it's not possible due to MockHttpServletRequestDsl internal constructor.

The closest information I found is from a kotlin slack-chats.

I intend to make Spring DSL class (like RouterFunctionDsl) constructors internal via internal constructor since they are intended to be used only via builder functions like router { }, bean { } or extensions like mockMvc.get(). Any objection?

Comment From: sdeleuze

Those DSL constructors are internal in order to avoid polluting Spring Framework Kotlin public API, they are designed to be extended using extensions.

Yes, some things could be done using the extensions, e.g. fun ContentResultMatchersDsl.myObject(smth). But it's not possible to do so with a context.

Could you please elaborate more on this, maybe providing code samples of the feature you want to provide to your users?

Comment From: SergeiKhmelevSPA

This is what I'm currently using

@Component
class MockMvcHelper(
  private val mockMvc: MockMvc,
  private val objectMapper: ObjectMapper,
) {

  fun post(urlTemplate: String, vararg vars: Any?, dsl: MyMockHttpServletRequestDsl.() -> Unit = {}): ResultActionsDsl =
    mockMvc.post(urlTemplate, *vars, dsl = MyMockHttpServletRequestDsl().apply(dsl).build(objectMapper))

  class MyMockHttpServletRequestDsl(
    var accept: MediaType? = MediaType.APPLICATION_JSON,
    var contentType: MediaType? = MediaType.APPLICATION_JSON,
    var body: Any? = null,
  ) {

    private var authorities: Array<GrantedAuthority>? = null

    fun authorities(vararg authorities: GrantedAuthority) {
      this.authorities = arrayOf(*authorities)
    }

    internal fun build(objectMapper: ObjectMapper): MockHttpServletRequestDsl.() -> Unit = {
      this@MyMockHttpServletRequestDsl.accept?.let { accept = it }
      this@MyMockHttpServletRequestDsl.contentType?.let { contentType = it }
      this@MyMockHttpServletRequestDsl.body?.let { content = objectMapper.writeValueAsString(it) }
      this@MyMockHttpServletRequestDsl.authorities?.let { setJwtToken(*it) }
    }
  }
}

And this is what I'd like to have instead:

class MyMockHttpServletRequestDsl(
  builder: MockHttpServletRequestBuilder,
) : MockHttpServletRequestDsl(builder) {

  override var accept: MediaType? = MediaType.APPLICATION_JSON
  override var contentType: MediaType? = MediaType.APPLICATION_JSON
  var body: Any? = null
  private var authorities: Array<GrantedAuthority>? = null

  fun authorities(vararg authorities: GrantedAuthority) {
    this.authorities = arrayOf(*authorities)
  }

  internal fun perform(objectMapper: ObjectMapper, mockMvc: MockMvc): ResultActionsDsl {
    body?.let { content = objectMapper.writeValueAsString(it) }
    authorities?.let { setJwtToken(*it) }
    return super.perform(mockMvc)
  }
}

Additionally would wrap a ResultActionsDsl so it contains an object mapper. return MyResultActionsDsl(super.perform(mockMvc), objectMapper).

Comment From: sdeleuze

Thanks for providing more details. I am not in favor of introducing a public constructor due to the API pollution problem I mentioned. The regular use case is to extend MockHttpServletRequestDsl via an extension since it is open. For your use case, that indeed does not play well. That's not ideal, but maybe just copy MockHttpServletRequestDsl and adapt it to your need, or just pass objectMapper as parameter.