SpringのBean Definition DSL で登録したComponent をテストで使用する方法

# Bean Definition DSL とは
https://docs.spring.io/spring-framework/docs/current/reference/html/languages.html#kotlin-bean-definition-dsl
Spring の Kotlinサポート機能で、SpringコンポーネントをDSLで登録する方法です。
従来の `@Component` などのように宣言的にコンポーネント登録せず、明示的にコンポーネントを登録していきます。

- 従来のコンポーネント登録
```kotlin
@Configuration
class Config {
    @Bean
    fun myHandler() = MyHandler()
}
```
とか
- 従来のコンポーネント登録その2
```kotlin
@Component
class MyHandler {
    fun findAll(req: ServerRequest) = ok().body("hello", String::class.java)
}
```
といったいつものやつに対して、Bean Definition DSLはアノテーションを使用せず

- Bean Definition DSL によるコンポーネント登録
```kotlin
@SpringBootApplication
class Application

fun main(args: Array<String>) {
    runApplication<Application>(*args) {
        addInitializers(beans);
    }
}

private val beans: BeanDefinitionDsl = beans {
    bean<MyHandler>()
}
```
こんな感じで、 アノテーションではなく、 `bean<型>()` として明示的に（？）コンポーネントを登録していきます。
`addInitializers(beans)` これで実際にContextに対してBean登録されてます。

このやり方はコンポーネントスキャンベースと比較して起動速度に関しては優位でもなさそうなのであまりメリットはわからないです。
https://towardsdatascience.com/a-benchmark-of-spring-annotations-vs-kotlin-dsl-209f54294325

- RouterFunctionDSL との組み合わせ
```kotlin
@SpringBootApplication
class Application

fun main(args: Array<String>) {
    runApplication<Application>(*args) {
        addInitializers(beans);
    }
}

private val beans: BeanDefinitionDsl = beans {
    bean<MyHandler>()
    bean { route(ref()) }
}

private fun route(myHandler: MyHandler): RouterFunction<ServerResponse> = router {
    GET("/", myHandler::findAll)
}
```
とかDSLの組み合わせで小規模なAPIサーバーをサクッと作れるのが楽なのかもしれないです。

# 困ったこと
## 事象
テストでBeanが登録されない！！

```kotlin
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ApplicationTests(
    private val webTestClient: WebTestClient,
) : FunSpec({
    test("access top") {
        webTestClient
            .get()
            .uri("/")
            .accept(MediaType.APPLICATION_JSON)
            .exchange()
            .expectStatus().isOk
    }
```

こんなtestを実行しても、404になりテストが通りませんでした。
`route` 関数で設定した RouterFunction がコンポーネントとして登録されていなそうでした。

```kotlin
@SpringBootApplication
class Application

fun main(args: Array<String>) {
    runApplication<Application>(*args) {
        addInitializers(beans);
    }
}

private val beans: BeanDefinitionDsl = beans {
    bean<MyHandler>()
    bean { route(ref()) }
}

private fun route(myHandler: MyHandler): RouterFunction<ServerResponse> = router {
    GET("/", myHandler::findAll)
}
```

アノテーションベースのSpringTestならいい感じにコンポーネント登録してくれているので、頭がフリーズします。

## 原因
```kotlin
fun main(args: Array<String>) {
    runApplication<Application>(*args) {
        addInitializers(beans);
    }
}
```
メイン関数内で　`addInitializers(beans: BeanDefinitionDsl)` してるからでした。
SpringExtension　はプロダクションコードのメイン関数を呼び出しているわけではないので、当たり前といえば当たり前でした。

# 解決方針
## テストコードもプロダクションコードと同じコンポーネントを指定
プロダクションコードがテストコードと同じコンポーネントを指定する必要があるなら、Beanを外部ファイルも読み取れるスコープに配置し
テストコードは `@ContextConfiguration` で initializers に指定すればよい
ただし、 `@ContextConfiguration.initializers` が求める型は `Class<? extends ApplicationContextInitializer<?>>[]` なので、
```kotlin
val beans = beans {
    bean<MyHandler>()
    bean { route(ref()) }
}
```
と書かれた `beans` を直接参照することはできない。

仕方がないので
```kotlin
class MyContextInitializer : ApplicationContextInitializer<GenericApplicationContext> {
    override fun initialize(applicationContext: GenericApplicationContext) {
        beans.initialize(applicationContext)
    }
}

private val beans: BeanDefinitionDsl = beans {
    bean<MyHandler>()
    bean { route(ref()) }
}

fun route(myHandler: MyHandler): RouterFunction<ServerResponse> = router {
    GET("/", myHandler::findAll)
}
```

Bean Definition DSL を context に登録するような `ApplicationContextInitializer` を実装します。 

- プロダクションコード
```kotlin

@SpringBootApplication
class Application

fun main(args: Array<String>) {
    runApplication<Application>(*args) {
        addInitializers(MyContextInitializer());
    }
}
```

- テストコード
```kotlin
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ContextConfiguration(initializers = [MyContextInitializer::class])
class ApplicationTests(
```

とすることで無事にプロダクションとテストで同じコンポーネントを生成できました。

# 疑問
「テストコードの一部コンポーネントのみ動作を変えたbeanをinjectしたい。」みたいなニーズってあると思うのですが、その場合はどうするのが正しいかがわからないです。
アノテーションベースなら `@Conditional` みたいなアノテーションで1発にも思えるのですが、なにかうまいやり方があるのか。