Created
October 23, 2019 07:59
-
-
Save KaneCheshire/8fc30a6f81116829ea7b6024431665df to your computer and use it in GitHub Desktop.
Example on how to mock JSONDecoder for tests
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
final class MockDecoder: Decoder { | |
var valuesForKeys: [String: Decodable] = [:] | |
func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key: CodingKey { | |
return KeyedDecodingContainer(MockContainer(valuesForKeys: valuesForKeys)) | |
} | |
let codingPath: [CodingKey] = [] | |
let userInfo: [CodingUserInfoKey: Any] = [:] | |
func unkeyedContainer() throws -> UnkeyedDecodingContainer { fatalError() } | |
func singleValueContainer() throws -> SingleValueDecodingContainer { fatalError() } | |
} | |
extension MockDecoder { | |
struct MockContainer<Keys: CodingKey>: KeyedDecodingContainerProtocol { | |
let valuesForKeys: [String: Decodable] | |
let codingPath: [CodingKey] = [] | |
let allKeys: [Keys] = [] | |
var context: DecodingError.Context { return DecodingError.Context(codingPath: codingPath, debugDescription: "") } | |
func decode(_ type: String.Type, forKey key: Keys) throws -> String { | |
guard let value = valuesForKeys[key.stringValue] else { throw DecodingError.keyNotFound(key, context) } | |
guard let string = value as? String else { throw DecodingError.valueNotFound(type, context) } | |
return string | |
} | |
func decode<T>(_ type: T.Type, forKey key: Keys) throws -> T where T: Decodable { | |
guard let value = valuesForKeys[key.stringValue] else { throw DecodingError.keyNotFound(key, context) } | |
guard let decodable = value as? T else { throw DecodingError.valueNotFound(type, context) } | |
return decodable | |
} | |
func contains(_ key: Key) -> Bool { fatalError() } | |
func decodeNil(forKey key: Keys) throws -> Bool { fatalError() } | |
func decode(_ type: Bool.Type, forKey key: Keys) throws -> Bool { fatalError() } | |
func decode(_ type: Double.Type, forKey key: Keys) throws -> Double { fatalError() } | |
func decode(_ type: Float.Type, forKey key: Keys) throws -> Float { fatalError() } | |
func decode(_ type: Int.Type, forKey key: Keys) throws -> Int { fatalError() } | |
func decode(_ type: Int16.Type, forKey key: Keys) throws -> Int16 { fatalError() } | |
func decode(_ type: Int8.Type, forKey key: Keys) throws -> Int8 { fatalError() } | |
func decode(_ type: Int32.Type, forKey key: Keys) throws -> Int32 { fatalError() } | |
func decode(_ type: Int64.Type, forKey key: Keys) throws -> Int64 { fatalError() } | |
func decode(_ type: UInt.Type, forKey key: Keys) throws -> UInt { fatalError() } | |
func decode(_ type: UInt8.Type, forKey key: Keys) throws -> UInt8 { fatalError() } | |
func decode(_ type: UInt16.Type, forKey key: Keys) throws -> UInt16 { fatalError() } | |
func decode(_ type: UInt32.Type, forKey key: Keys) throws -> UInt32 { fatalError() } | |
func decode(_ type: UInt64.Type, forKey key: Keys) throws -> UInt64 { fatalError() } | |
func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: Keys) throws -> KeyedDecodingContainer<NestedKey> where NestedKey: CodingKey { fatalError() } | |
func nestedUnkeyedContainer(forKey key: Keys) throws -> UnkeyedDecodingContainer { fatalError() } | |
func superDecoder() throws -> Decoder { fatalError() } | |
func superDecoder(forKey key: Keys) throws -> Decoder { fatalError() } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Usage:
Additionally, if you've exposed your
CodingKeys
enum in your model object, you can make it conform toCaseIterable
and then loop through so you get compiler help if you add or remove a case from the enum:Then in your tests you'd just call the
init
of yourDecodable
object by passing in our mock decoder, rather than the regularJSONDecoder
, removing or changing values for keys as you see fit:You could also then extend this to control what errors get thrown, and support more of the decode functions (which I didn't need for my tests so left them with
fatalError
for now).