I have some Swift models that encode data to json. For example:
struct ExampleModel: Encodable {
var myComputedProperty: Bool { dependentModel.first(where: { $0.hasTrueProperty}) }
enum CodingKeys: String, CodingKey {
case firstKey = "first_key"
case secondKey = "second_key"
case myComputedProperty = "my_computed_property"
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(firstKey, forKey: .firstKey)
try container.encode(myComputedProperty, forKey: .myComputedProperty)
}
}
The encoded data is sent to an API, and cross-platform system tests in this case are logistically tricky, so it's important I write tests that ensure the encoded data is as expected. All I'm looking to do is ensure container.encode
receives expected keys and values.
I'm somewhat new to Swift, and trying to override the container, its dependencies & generics, and its .encode method is taking me down a rabbit-hole of rewriting half Swift's encoding foundation. In short: the spy I'm writing is too complex to be useful.
Despite lack of Google/StackOverflow results, I'm guessing spying on encoders is common (?), and that there's an easier way to confirm container.encode
receives expected values. But the way swift's Encoder functionality is written is making it hard for me to do so without rewriting half the encoder. Anyone have boilerplate code or an example of effectively spying on container.encode
?
I have never done tests spying on encoder, although I have plenty of tests checking correction of encoding/decoding.
There are multiple ways to do it:
But the most basic way to test is encoding and then decoding your data structure, and comparing the structures. They should be identical.
How it's done:
Decodable
extension for your struct
(or if your struct
is Codable
, then you already ave this). It can be added directly in the test class / test target if you don't need it in the production code:extension ExampleModel: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
firstKey = try container.decode(<...>.self, forKey: .firstKey)
// etc
}
}
Equatable
extension for the struct
:extension ExampleModel: Equatable {
// Rely on synthesized comparison, or create your own
}
let given = ExampleModel(firstKey: ..., ...)
let whenEncoded = try JSONEncoder().encode(given)
let whenDecoded = try JSONDecoder().decode(ExampleModel.self, from: whenEncoded)
// Then
XCTAssertEqual(given, whenDecoded)
Couple of notes:
encode
, but before it was sent to API? Better solution is to make a let
property in the struct, but have an option to create a struct with this value given with appropriate calculation (e.g. init
for ExampleModel
could be passing the dependentModel
, and the property would be calculated in the init once)