You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Micronaut Gradle API测试因查询参数映射数据类失败

Micronaut Kotlin服务Spek测试请求失败(Bad Request)问题

我维护一个基于Micronaut Kotlin的遗留服务,基础功能正常:直接通过http://localhost:{port}/api/v1/test或带查询参数的http://localhost:{port}/api/v1/test?size=1&number=50&sort=ASC请求API,都能正常响应。但运行Spek测试时请求失败,抛出以下异常:

Bad Request
io.micronaut.http.client.exceptions.HttpClientResponseException: Bad Request
at io.micronaut.http.client.netty.DefaultHttpClient$FullHttpResponseHandler.makeErrorFromRequestBody(DefaultHttpClient.java:2232)

测试时请求永远无法到达资源类中return HttpResponse.ok(mutableListOf("hello", "world"))这一行的断点。但如果把API方法的参数改成String类型:

fun listTestValues(@Parameter(required = false, `in` = ParameterIn.QUERY) pagingQuery: String)

调试器能正常停在对应的断点处。


核心代码结构

TestApi接口

interface TestApi {
    fun listTestValues(@Parameter(required = false, `in` = ParameterIn.QUERY) pagingQuery: PagingQueryWS): HttpResponse<List<*>>
}

TestResource控制器

@Secured(SecurityRule.IS_AUTHENTICATED)
@Controller(
    "/api/v1/test",
    consumes = [MediaType.APPLICATION_JSON],
    produces = [MediaType.APPLICATION_JSON]
)
class TestResource: TestApi {
    @Get("{?pagingQuery*}")
    override fun listTestValues(pagingQuery: PagingQueryWS): HttpResponse<List<*>> {
        return HttpResponse.ok(mutableListOf("hello", "world"))
    }
}

Spek测试类

class TestResourceUT: Spek({
    Feature("Test Resource UT") {
        val applicationContext =
            ApplicationContext.builder().build().start()
        val embeddedServer = applicationContext.getBean(EmbeddedServer::class.java).start()
        val client = HttpClient.create(embeddedServer.url)

        Scenario("GET test api") {
            lateinit var returned: List<*>
            When("Responds successfully") {
                val request = HttpRequest.GET<List<String>>("/api/v1/test")
                returned = client
                    .toBlocking()
                    .retrieve(request, List::class.java)
            }
            Then("A List is returned with the found values") {
                assertThat(returned.size).isEqualTo(2)
            }
        }

        afterGroup {
            client.close()
            embeddedServer.close()
        }
    }
})

分页参数类

@Introspected
data class PagingQueryWS(
    override val number: Int = PagingQuery.DEFAULT_PAGE_NUMBER,
    override val size: Int = PagingQuery.DEFAULT_PAGE_SIZE,
    override val sort: String? = null
): PagingQuery

interface PagingQuery {
    val number: Int
    val size: Int
    val sort: String?

    private fun getSort(): Sort {
        return sort
            ?.split(",")
            ?.map {
                val direction = it[0]
                val field = it.substring(1)

                if (direction == '-') {
                    Order.desc(field)
                } else {
                    Order.asc(field)
                }
            }
            ?.let {
                Sort.of(it)
            }
            ?: Sort.UNSORTED
    }

    // External pages are 1-indexed but internal are 0-indexed
    fun toPageable(): Pageable = Pageable.from(number - 1, size, getSort())

    companion object {
        const val DEFAULT_PAGE_SIZE = 50
        const val DEFAULT_PAGE_NUMBER = 1
    }
}

使用版本

  • Kotlin 1.9.25
  • Micronaut 3.9.7
  • Spek 2.0.7

内容的提问来源于stack exchange,提问作者Kammryn Dancy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 23:36:18