Swift测试:如何监听Encoder的container.encode调用?
监听Swift Encoder中container.encode的简易方案
不用重写整套Swift编码基础库,只需要实现一个轻量的记录型编码容器,就能轻松捕获container.encode调用的键和值。
实现步骤
1. 记录型键值编码容器
这个容器会把所有调用encode的键值对存储到字典中,用于后续验证:
class RecordingKeyedEncodingContainer<K: CodingKey>: KeyedEncodingContainerProtocol { typealias Key = K // 存储所有编码的键值对 private(set) var encodedValues: [String: Any] = [:] var codingPath: [CodingKey] = [] func encodeNil(forKey key: Key) throws {} func encode<T>(_ value: T, forKey key: Key) throws where T : Encodable { // 处理基础可哈希类型 if let hashableValue = value as? AnyHashable { encodedValues[key.stringValue] = hashableValue.base } else { // 处理嵌套的Encodable类型,递归记录 let nestedContainer = RecordingKeyedEncodingContainer<K>() try value.encode(to: RecordingEncoder(container: nestedContainer)) encodedValues[key.stringValue] = nestedContainer.encodedValues } } // 以下方法按需实现,这里提供基础逻辑 func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey { let container = RecordingKeyedEncodingContainer<NestedKey>() return KeyedEncodingContainer(container) } func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { return RecordingUnkeyedContainer() } func superEncoder() -> Encoder { return RecordingEncoder(container: RecordingKeyedEncodingContainer<K>()) } func superEncoder(forKey key: Key) -> Encoder { return RecordingEncoder(container: RecordingKeyedEncodingContainer<K>()) } } // 配套处理数组等无键容器的基础实现 class RecordingUnkeyedContainer: UnkeyedEncodingContainer { var codingPath: [CodingKey] = [] var count: Int? = 0 func encodeNil() throws {} func encode<T>(_ value: T) throws where T : Encodable {} func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey { return KeyedEncodingContainer(RecordingKeyedEncodingContainer<NestedKey>()) } func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { return self } func superEncoder() -> Encoder { return RecordingEncoder(container: RecordingKeyedEncodingContainer<CodingKey>()) } }
2. 极简的记录编码器
配合容器实现最基础的Encoder协议,只需要提供获取容器的方法:
class RecordingEncoder: Encoder { var codingPath: [CodingKey] = [] var userInfo: [CodingUserInfoKey : Any] = [:] private let container: Any init<K: CodingKey>(container: RecordingKeyedEncodingContainer<K>) { self.container = container } func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey { if let typedContainer = container as? RecordingKeyedEncodingContainer<Key> { return KeyedEncodingContainer(typedContainer) } return KeyedEncodingContainer(RecordingKeyedEncodingContainer<Key>()) } func unkeyedContainer() -> UnkeyedEncodingContainer { return RecordingUnkeyedContainer() } func singleValueContainer() -> SingleValueEncodingContainer { return RecordingSingleValueContainer() } } // 单值容器的基础实现 class RecordingSingleValueContainer: SingleValueEncodingContainer { var codingPath: [CodingKey] = [] func encodeNil() throws {} func encode<T>(_ value: T) throws where T : Encodable {} }
3. 测试中使用
在单元测试里,用这个记录编码器执行模型的编码,然后直接验证存储的键值对:
func testExampleModelEncoding() throws { // 初始化测试模型(根据你的实际属性调整) let dependentModel = DependentModel(hasTrueProperty: true) let model = ExampleModel(firstKey: "api_test_value", dependentModel: [dependentModel]) // 创建记录容器和编码器 let recordingContainer = RecordingKeyedEncodingContainer<ExampleModel.CodingKeys>() let encoder = RecordingEncoder(container: recordingContainer) // 执行编码逻辑 try model.encode(to: encoder) // 验证预期的键值对 XCTAssertEqual(recordingContainer.encodedValues["first_key"] as? String, "api_test_value") XCTAssertEqual(recordingContainer.encodedValues["my_computed_property"] as? Bool, true) // 验证未被编码的键(比如secondKey)不存在 XCTAssertNil(recordingContainer.encodedValues["second_key"]) }
关键优势
- 只实现核心记录逻辑,无需重写整套编码库
- 支持嵌套模型的递归记录,覆盖多数常见场景
- 测试逻辑直观,直接验证存储的键值对即可
内容的提问来源于stack exchange,提问作者PlankTon
相关产品推荐
相关产品推荐

