@@ -87,3 +87,114 @@ extension CurrencyAmount: ExpressibleByFloatLiteral {
8787 self . value = Decimal ( value)
8888 }
8989}
90+
91+ private func assertThat( _ dictionary: [ String : Any ] ,
92+ file: StaticString = #file,
93+ line: UInt = #line) -> DictionarySubject {
94+ return DictionarySubject ( dictionary, file: file, line: line)
95+ }
96+
97+ func assertThat< X: Equatable & Codable > ( _ model: X , file: StaticString = #file,
98+ line: UInt = #line) -> CodableSubject < X > {
99+ return CodableSubject ( model, file: file, line: line)
100+ }
101+
102+ func assertThat< X: Equatable & Encodable > ( _ model: X , file: StaticString = #file,
103+ line: UInt = #line) -> EncodableSubject < X > {
104+ return EncodableSubject ( model, file: file, line: line)
105+ }
106+
107+ class EncodableSubject < X: Equatable & Encodable > {
108+ var subject : X
109+ var file : StaticString
110+ var line : UInt
111+
112+ init ( _ subject: X , file: StaticString , line: UInt ) {
113+ self . subject = subject
114+ self . file = file
115+ self . line = line
116+ }
117+
118+ @discardableResult
119+ func encodes( to expected: [ String : Any ] ,
120+ using encoder: Database . Encoder = . init( ) ) -> DictionarySubject {
121+ let encoded = assertEncodes ( to: expected, using: encoder)
122+ return DictionarySubject ( encoded, file: file, line: line)
123+ }
124+
125+ func failsToEncode( ) {
126+ do {
127+ let encoder = Database . Encoder ( )
128+ encoder. keyEncodingStrategy = . convertToSnakeCase
129+ _ = try encoder. encode ( subject)
130+ } catch {
131+ return
132+ }
133+ XCTFail ( " Failed to throw " )
134+ }
135+
136+ func failsEncodingAtTopLevel( ) {
137+ do {
138+ let encoder = Database . Encoder ( )
139+ encoder. keyEncodingStrategy = . convertToSnakeCase
140+ _ = try encoder. encode ( subject)
141+ XCTFail ( " Failed to throw " , file: file, line: line)
142+ } catch EncodingError . invalidValue( _, _) {
143+ return
144+ } catch {
145+ XCTFail ( " Unrecognized error: \( error) " , file: file, line: line)
146+ }
147+ }
148+
149+ private func assertEncodes( to expected: [ String : Any ] ,
150+ using encoder: Database . Encoder = . init( ) ) -> [ String : Any ] {
151+ do {
152+ let enc = try encoder. encode ( subject)
153+ XCTAssertEqual ( enc as? NSDictionary , expected as NSDictionary , file: file, line: line)
154+ return ( enc as! NSDictionary ) as! [ String : Any ]
155+ } catch {
156+ XCTFail ( " Failed to encode \( X . self) : error: \( error) " )
157+ return [ " " : - 1 ]
158+ }
159+ }
160+ }
161+
162+ class CodableSubject < X: Equatable & Codable > : EncodableSubject < X > {
163+ func roundTrips( to expected: [ String : Any ] ,
164+ using encoder: Database . Encoder = . init( ) ,
165+ decoder: Database . Decoder = . init( ) ) {
166+ let reverseSubject = encodes ( to: expected, using: encoder)
167+ reverseSubject. decodes ( to: subject, using: decoder)
168+ }
169+ }
170+
171+ class DictionarySubject {
172+ var subject : [ String : Any ]
173+ var file : StaticString
174+ var line : UInt
175+
176+ init ( _ subject: [ String : Any ] , file: StaticString , line: UInt ) {
177+ self . subject = subject
178+ self . file = file
179+ self . line = line
180+ }
181+
182+ func decodes< X: Equatable & Codable > ( to expected: X ,
183+ using decoder: Database . Decoder = . init( ) ) -> Void {
184+ do {
185+ let decoded = try decoder. decode ( X . self, from: subject)
186+ XCTAssertEqual ( decoded, expected)
187+ } catch {
188+ XCTFail ( " Failed to decode \( X . self) : \( error) " , file: file, line: line)
189+ }
190+ }
191+
192+ func failsDecoding< X: Equatable & Codable > ( to _: X . Type ,
193+ using decoder: Database . Decoder = . init( ) ) -> Void {
194+ XCTAssertThrowsError (
195+ try decoder. decode ( X . self, from: subject) ,
196+ file: file,
197+ line: line
198+ )
199+ }
200+ }
0 commit comments