所以,我有一个看起来像这样的类型:
struct Identifier {
let string: String
}
extension Identifier: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
string = try container.decode(String.self)
}
}
这种类型的要点是,如果我有如下所示的 JSON:
{
"identifier": "abc123",
// more properties ...
}
。它将自动序列化为正确的类型,而无需太多努力。但是,我在不创建包装类型的情况下对Decodable
的这种一致性进行单元测试时遇到了问题。
我想做的是这样的:
func testDecodableInit() {
let identifier = try! JSONDecoder().decode(Identifier.self, from: "1".data(using: .utf8)!)
XCTAssertEqual(identifier.string, "1")
}
但显然这是行不通的"1"
因为 JSON 不是有效的 JSON。
是否可以在不创建包装类型并将数据更改为有效 JSON 的情况下为这种一致性编写单元测试以Decodable
?
如果有人想知道如何使用包装类型创建测试。它看起来像这样;
struct Identifier {
let string: String
}
extension Identifier: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
string = try container.decode(String.self)
}
}
我们的测试将如下所示;
class IdentifierTests: XCTestCase {
func testStringValueDecodedSuccessfully() throws {
let decoder = JSONDecoder()
let data = Data("{"value": "identifier-string"}".utf8)
let container = try decoder.decode(Wrapper1.self, from: data)
XCTAssertEqual(container.identifierValue.string, "identifier-string")
}
}
private struct Wrapper: Decodable {
let identifierValue: Identifier
enum CodingKeys: String, CodingKey {
case value
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
identifierValue = try container.decode(Identifier.self, forKey: .value)
}
}
我放弃了在不创建包装类型的情况下完成此操作的尝试,因为假设很难解码一个一开始就不是有效 JSON 的字符串(在我的示例中'1'
(。
所以,我想,答案是:只需创建一个包装类型。 ̄\_(ツ)_/̄
另一种选择。这是更多的代码,因为您必须实现Decoder
和SingleValueDecodingContainer
协议的所有方法,但测试看起来更干净:
就我而言,我必须解析值,而不仅仅是存储它。因此,测试的原因。
您的测试如下所示:
func testDecode() throws {
let value = try Identifier(from: StringDecoder(text: "1"))
XCTAssertEqual(value.string, "1")
}
我的测试如下所示:
func testDecode() throws {
let yearMonth = try YearMonth(from: StringDecoder(text: "2022-02-01T00:00:00"))
XCTAssertEqual(yearMonth, YearMonth(year: 2022, month: 2))
}
支持代码如下。值得吗?你是法官。
struct StringDecoder: Decoder {
let text: String
var codingPath: [CodingKey] { XCTFail(); return [] }
var userInfo: [CodingUserInfoKey : Any] { XCTFail(); return [:] }
init(text: String) {
self.text = text
}
func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey {
throw BadDecode()
}
func unkeyedContainer() throws -> UnkeyedDecodingContainer {
throw BadDecode()
}
func singleValueContainer() throws -> SingleValueDecodingContainer {
ValueDecoder(text: text)
}
}
struct ValueDecoder: SingleValueDecodingContainer {
let text: String
var codingPath: [CodingKey] { XCTFail(); return [] }
func decodeNil() -> Bool {
XCTFail()
return false
}
func decode(_ type: Bool.Type) throws -> Bool {
throw BadDecode()
}
func decode(_ type: String.Type) throws -> String {
return text
}
func decode(_ type: Double.Type) throws -> Double {
throw BadDecode()
}
func decode(_ type: Float.Type) throws -> Float {
throw BadDecode()
}
func decode(_ type: Int.Type) throws -> Int {
throw BadDecode()
}
func decode(_ type: Int8.Type) throws -> Int8 {
throw BadDecode()
}
func decode(_ type: Int16.Type) throws -> Int16 {
throw BadDecode()
}
func decode(_ type: Int32.Type) throws -> Int32 {
throw BadDecode()
}
func decode(_ type: Int64.Type) throws -> Int64 {
throw BadDecode()
}
func decode(_ type: UInt.Type) throws -> UInt {
throw BadDecode()
}
func decode(_ type: UInt8.Type) throws -> UInt8 {
throw BadDecode()
}
func decode(_ type: UInt16.Type) throws -> UInt16 {
throw BadDecode()
}
func decode(_ type: UInt32.Type) throws -> UInt32 {
throw BadDecode()
}
func decode(_ type: UInt64.Type) throws -> UInt64 {
throw BadDecode()
}
func decode<T>(_ type: T.Type) throws -> T where T : Decodable {
throw BadDecode()
}
}
struct BadDecode: Error { }