如何用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
相关产品推荐
相关产品推荐

