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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 14:35:40