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

如何用MockK Mock Kotlin类内部对象?解决Lambda测试报错问题

如何可靠测试直接初始化LambdaClient的AWS Lambda Handler(MockK)

问题背景

测试直接初始化LambdaClient的FooHandler时,MockK无法创建代理实例,抛出以下错误:

io.mockk.MockKException: Can't instantiate proxy via default constructor for class FooHandler
Caused by: software.amazon.awssdk.core.exception.SdkClientException: Unable to load region from any of the providers in the chain software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain@7668d560

根源是FooHandler内部直接调用LambdaClient.create(),测试环境未配置AWS区域,导致初始化客户端失败,而MockK在创建Spy时会触发类的初始化逻辑。

原代码示例

Lambda Handler代码

class FooHandler: RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

    private val lambdaClient = LambdaClient.create()
    private val logger = LogManager.getLogger(CollectorHandler::class)

    override fun handleRequest(request: APIGatewayProxyRequestEvent, context: Context): APIGatewayProxyResponseEvent {
        val response = APIGatewayProxyResponseEvent()
        if (isRequiredHeaderEmpty(request, response) || isBodyEmpty(request, response)) {
            return response
        }
        return response
    }
}

原测试代码

class FooHandlerTest {

    private val request = APIGatewayProxyRequestEvent()
    private var response = APIGatewayProxyResponseEvent()
    @SpyK
    private lateinit var handler: FooHandler
    @MockK
    private lateinit var lambdaClient: LambdaClient
    @MockK
    private lateinit var context: Context

    @BeforeTest
    fun setUp() {
        handler = spyk()
        lambdaClient = mockk()
        context = mockk()
    }

    @Test
    fun testHandleRequestWhenBodyIsEmpty(){
        request.body=""
        response = handler.handleRequest(request, context)
        expectThat(response.statusCode).isEqualTo(HttpStatus.BAD_REQUEST)
    }
}

解决方案:依赖注入重构(推荐)

核心思路是把LambdaClient的初始化逻辑从Handler内部抽离,通过构造注入的方式传入,让测试时可以替换为Mock实例。

1. 修改Handler类,添加构造注入支持

保留默认构造函数兼容Lambda部署,新增带参数的构造函数用于测试:

class FooHandler: RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

    private val lambdaClient: LambdaClient
    private val logger = LogManager.getLogger(FooHandler::class)

    // 测试用构造函数:注入LambdaClient实例
    constructor(lambdaClient: LambdaClient) {
        this.lambdaClient = lambdaClient
    }

    // 生产用默认构造函数:保持原有初始化逻辑
    constructor() {
        this.lambdaClient = LambdaClient.create()
    }

    override fun handleRequest(request: APIGatewayProxyRequestEvent, context: Context): APIGatewayProxyResponseEvent {
        val response = APIGatewayProxyResponseEvent()
        if (isRequiredHeaderEmpty(request, response) || isBodyEmpty(request, response)) {
            return response
        }
        // 后续如果有lambdaClient的调用逻辑,也能被Mock拦截
        return response
    }

    // 示例辅助方法(假设原有逻辑)
    private fun isRequiredHeaderEmpty(request: APIGatewayProxyRequestEvent, response: APIGatewayProxyResponseEvent): Boolean {
        // 检查必填Header逻辑,若为空则设置400状态码
        return false
    }

    private fun isBodyEmpty(request: APIGatewayProxyRequestEvent, response: APIGatewayProxyResponseEvent): Boolean {
        if (request.body.isNullOrEmpty()) {
            response.statusCode = HttpStatus.BAD_REQUEST.value()
            return true
        }
        return false
    }
}

2. 修改测试类,注入Mock的LambdaClient

无需使用Spy,直接通过构造函数传入Mock实例:

class FooHandlerTest {

    private val request = APIGatewayProxyRequestEvent()
    private lateinit var context: Context
    private lateinit var mockLambdaClient: LambdaClient
    private lateinit var handler: FooHandler

    @BeforeTest
    fun setUp() {
        // 创建LambdaClient的Mock实例(relaxed=true避免未存根方法报错)
        mockLambdaClient = mockk(relaxed = true)
        context = mockk(relaxed = true)
        // 通过测试构造函数创建Handler,注入Mock客户端
        handler = FooHandler(mockLambdaClient)
    }

    @Test
    fun testHandleRequestWhenBodyIsEmpty() {
        request.body = ""
        val response = handler.handleRequest(request, context)
        // 断言返回400状态码
        assertThat(response.statusCode).isEqualTo(HttpStatus.BAD_REQUEST.value())
    }
}

临时方案:静态Mock(不推荐)

如果无法修改Handler代码,可以用MockK的静态Mock拦截LambdaClient.create()方法,但这种方式容易导致测试间污染,仅作为临时方案:

class FooHandlerTest {

    private val request = APIGatewayProxyRequestEvent()
    private lateinit var handler: FooHandler
    private lateinit var context: Context

    @BeforeTest
    fun setUp() {
        // 静态Mock LambdaClient的create方法
        mockkStatic(LambdaClient::class)
        every { LambdaClient.create() } returns mockk(relaxed = true)
        // 此时创建Handler时,create()会返回Mock实例
        handler = FooHandler()
        context = mockk(relaxed = true)
    }

    @AfterTest
    fun tearDown() {
        // 清理静态Mock,避免影响其他测试
        unmockkStatic(LambdaClient::class)
    }

    @Test
    fun testHandleRequestWhenBodyIsEmpty() {
        request.body = ""
        val response = handler.handleRequest(request, context)
        assertThat(response.statusCode).isEqualTo(HttpStatus.BAD_REQUEST.value())
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 15:45:02