运行Mock测试调用ListAccessKeys时遭遇ExpiredToken错误求解决方案
解决模块级boto3客户端的测试桩问题(不修改原代码)
你遇到的核心问题是:handler.py在模块加载阶段就初始化了iam_client,此时测试的桩逻辑还未生效,导致代码调用了真实的AWS IAM客户端,触发令牌过期错误。以下是无需修改handler.py的解决方案:
方案1:在导入handler前完成boto3打桩
将boto3的桩逻辑移到导入handler的代码块内,确保模块初始化时使用的是桩客户端而非真实客户端:
import sys sys.path.insert(1, 'folder1/folder2/folder3') import unittest import boto3 from datetime import datetime from botocore.stub import Stubber from unittest import mock from unittest.mock import patch, MagicMock # 先配置环境变量,同时打桩boto3.client with mock.patch.dict('os.environ',{ 'ENVIRONMENT': 'us-east-1', 'NOTIFICATION_LAMBDA': '', 'NOTIFICATION_SENDER': '', 'PLATFORM_CONTACT': '', 'KEY_ROTATION_DL_TAG_NAME': '' }), patch('boto3.client') as mock_boto_client: # 创建IAM桩客户端并关联Stubber iam_client = boto3.client('iam') stubber = Stubber(iam_client) mock_boto_client.return_value = iam_client # 模拟handler中未初始化的secretmanager mock_secretmanager = MagicMock() mock_boto_client.side_effect = lambda service: mock_secretmanager if service == 'secretsmanager' else iam_client # 此时导入handler,模块级的iam_client会使用我们的桩 import handler class TestLambda(unittest.TestCase): def test_create_key(self): # 定义list_access_keys的桩响应 expected_list_response = { 'AccessKeyMetadata': [ { 'UserName': 'testUser', 'AccessKeyId': 'AKIA111111111EXAMPLE', 'Status': 'Active', 'CreateDate': datetime(2015, 1, 1) } ] } # 定义create_access_key的桩响应 expected_create_response = { 'AccessKey': { 'AccessKeyId': 'AKIA222222222EXAMPLE', 'SecretAccessKey': 'secret123' } } # 给Stubber添加预期调用 stubber.add_response('list_access_keys', expected_list_response, {'UserName': 'testUser'}) stubber.add_response('create_access_key', expected_create_response, {'UserName': 'testUser'}) # 激活Stubber并执行测试 with stubber: result = handler.create_key('testUser') # 验证返回值符合预期 self.assertEqual(result[0], "OK") self.assertIn("New Key created for IAM user 'testUser'", result[1]['subject']) self.assertIn("AKIA222222222EXAMPLE", result[1]['body']) # 验证secretmanager的方法调用 handler.secretmanager.describe_secret.assert_called_once_with(SecretId='testUser') handler.secretmanager.create_secret.assert_called_once_with(Name='testUser')
方案2:直接替换handler模块的iam_client实例
如果不想提前打桩,也可以在测试方法中直接替换已初始化的handler.iam_client:
import sys sys.path.insert(1, 'folder1/folder2/folder3') import unittest import boto3 from datetime import datetime from botocore.stub import Stubber from unittest import mock from unittest.mock import MagicMock with mock.patch.dict('os.environ',{ 'ENVIRONMENT': 'us-east-1', 'NOTIFICATION_LAMBDA': '', 'NOTIFICATION_SENDER': '', 'PLATFORM_CONTACT': '', 'KEY_ROTATION_DL_TAG_NAME': '' }): import handler class TestLambda(unittest.TestCase): def test_create_key(self): # 创建IAM桩客户端和Stubber iam_client = boto3.client('iam') stubber = Stubber(iam_client) # 定义桩响应 expected_list_response = { 'AccessKeyMetadata': [ { 'UserName': 'testUser', 'AccessKeyId': 'AKIA111111111EXAMPLE', 'Status': 'Active', 'CreateDate': datetime(2015, 1, 1) } ] } expected_create_response = { 'AccessKey': { 'AccessKeyId': 'AKIA222222222EXAMPLE', 'SecretAccessKey': 'secret123' } } # 添加预期调用 stubber.add_response('list_access_keys', expected_list_response, {'UserName': 'testUser'}) stubber.add_response('create_access_key', expected_create_response, {'UserName': 'testUser'}) # 替换handler里的iam_client和未定义的secretmanager handler.iam_client = iam_client handler.secretmanager = MagicMock() with stubber: result = handler.create_key('testUser') # 验证结果 self.assertEqual(result[0], "OK") self.assertIn("AKIA222222222EXAMPLE", result[1]['body'])
关键修改说明
- 提前打桩boto3:确保
handler.py初始化iam_client时使用的是桩客户端,避免调用真实AWS服务。 - 模拟secretmanager:原handler代码遗漏了
secretmanager的初始化逻辑,测试中用MagicMock模拟该对象,避免报错。 - 修正断言逻辑:原测试错误地断言
create_key返回值等于list_access_keys的响应,实际create_key返回的是("OK", {"subject": ..., "body": ...}),需调整断言匹配实际返回结构。
内容的提问来源于stack exchange,提问作者RunRabbit
相关产品推荐
相关产品推荐

