@@ -23,7 +23,7 @@ open class SnapshotTestCase: XCTestCase {
2323 /// - testName: The name of the test in which failure occurred. Defaults to the function name of the test case in which this function was called.
2424 /// - line: The line number on which failure occurred. Defaults to the line number on which this function was called.
2525 public func assertSnapshot< Value, Format> (
26- matching value: Value ,
26+ matching value: @autoclosure ( ) throws -> Value ,
2727 as snapshotting: Snapshotting < Value , Format > ,
2828 named name: String ? = nil ,
2929 record recording: Bool = false ,
@@ -33,114 +33,215 @@ open class SnapshotTestCase: XCTestCase {
3333 line: UInt = #line
3434 ) {
3535
36- let recording = recording || self . record
37-
38- do {
39- let fileUrl = URL ( fileURLWithPath: " \( file) " )
40- let fileName = fileUrl. deletingPathExtension ( ) . lastPathComponent
41- let directoryUrl = fileUrl. deletingLastPathComponent ( )
42- let snapshotDirectoryUrl : URL = directoryUrl
43- . appendingPathComponent ( " __Snapshots__ " )
44- . appendingPathComponent ( fileName)
45-
46- let identifier : String
47- if let name = name {
48- identifier = sanitizePathComponent ( name)
49- } else {
50- identifier = String ( counter)
51- counter += 1
52- }
36+ let failure = verifySnapshot (
37+ matching: value,
38+ as: snapshotting,
39+ named: name,
40+ record: recording,
41+ timeout: timeout,
42+ file: file,
43+ testName: testName,
44+ line: line
45+ )
46+ guard let message = failure else { return }
47+ XCTFail ( message, file: file, line: line)
48+ }
5349
54- let testName = sanitizePathComponent ( testName)
55- let snapshotFileUrl = snapshotDirectoryUrl
56- . appendingPathComponent ( " \( testName) . \( identifier) " )
57- . appendingPathExtension ( snapshotting. pathExtension ?? " " )
58- let fileManager = FileManager . default
59- try fileManager. createDirectory ( at: snapshotDirectoryUrl, withIntermediateDirectories: true )
60-
61- let tookSnapshot = self . expectation ( description: " Took snapshot " )
62- var optionalDiffable : Format ?
63- snapshotting. snapshot ( value) . run { b in
64- optionalDiffable = b
65- tookSnapshot. fulfill ( )
66- }
67- #if os(Linux)
68- self . waitForExpectations ( timeout: timeout)
69- #else
70- self . wait ( for: [ tookSnapshot] , timeout: timeout)
71- #endif
72-
73- guard let diffable = optionalDiffable else {
74- XCTFail ( " Couldn't snapshot value " , file: file, line: line)
75- return
76- }
50+ /// Asserts that a given value matches references on disk.
51+ ///
52+ /// - Parameters:
53+ /// - value: A value to compare against a reference.
54+ /// - snapshotting: An dictionnay of names and strategies for serializing, deserializing, and comparing values.
55+ /// - recording: Whether or not to record a new reference.
56+ /// - timeout: The amount of time a snapshot must be generated in.
57+ /// - file: The file in which failure occurred. Defaults to the file name of the test case in which this function was called.
58+ /// - testName: The name of the test in which failure occurred. Defaults to the function name of the test case in which this function was called.
59+ /// - line: The line number on which failure occurred. Defaults to the line number on which this function was called.
60+ public func assertSnapshots< Value, Format> (
61+ matching value: @autoclosure ( ) throws -> Value ,
62+ as strategies: [ String : Snapshotting < Value , Format > ] ,
63+ record recording: Bool = false ,
64+ timeout: TimeInterval = 5 ,
65+ file: StaticString = #file,
66+ testName: String = #function,
67+ line: UInt = #line
68+ ) {
7769
78- guard !recording, fileManager. fileExists ( atPath: snapshotFileUrl. path) else {
79- let diffMessage = ( try ? Data ( contentsOf: snapshotFileUrl) )
80- . flatMap { data in snapshotting. diffing. diff ( snapshotting. diffing. fromData ( data) , diffable) }
81- . map { diff, _ in diff. trimmingCharacters ( in: . whitespacesAndNewlines) }
82- ?? " Recorded snapshot: … "
70+ strategies. forEach { name, strategy in
71+ assertSnapshot (
72+ matching: value,
73+ as: strategy,
74+ named: name,
75+ record: recording,
76+ timeout: timeout,
77+ file: file,
78+ testName: testName,
79+ line: line
80+ )
81+ }
82+ }
8383
84- try snapshotting. diffing. toData ( diffable) . write ( to: snapshotFileUrl)
85- let message = recording
86- ? """
87- Record mode is on. Turn record mode off and re-run " \( testName) " to test against the newly-recorded snapshot.
84+ /// Asserts that a given value matches references on disk.
85+ ///
86+ /// - Parameters:
87+ /// - value: A value to compare against a reference.
88+ /// - snapshotting: An array of strategies for serializing, deserializing, and comparing values.
89+ /// - recording: Whether or not to record a new reference.
90+ /// - timeout: The amount of time a snapshot must be generated in.
91+ /// - file: The file in which failure occurred. Defaults to the file name of the test case in which this function was called.
92+ /// - testName: The name of the test in which failure occurred. Defaults to the function name of the test case in which this function was called.
93+ /// - line: The line number on which failure occurred. Defaults to the line number on which this function was called.
94+ public func assertSnapshots< Value, Format> (
95+ matching value: @autoclosure ( ) throws -> Value ,
96+ as strategies: [ Snapshotting < Value , Format > ] ,
97+ record recording: Bool = false ,
98+ timeout: TimeInterval = 5 ,
99+ file: StaticString = #file,
100+ testName: String = #function,
101+ line: UInt = #line
102+ ) {
88103
89- open " \( snapshotFileUrl. path) "
104+ strategies. forEach { strategy in
105+ assertSnapshot (
106+ matching: value,
107+ as: strategy,
108+ record: recording,
109+ timeout: timeout,
110+ file: file,
111+ testName: testName,
112+ line: line
113+ )
114+ }
115+ }
116+
117+ /// Verifies that a given value matches a reference on disk.
118+ ///
119+ /// - Parameters:
120+ /// - value: A value to compare against a reference.
121+ /// - snapshotting: A strategy for serializing, deserializing, and comparing values.
122+ /// - name: An optional description of the snapshot.
123+ /// - recording: Whether or not to record a new reference.
124+ /// - timeout: The amount of time a snapshot must be generated in.
125+ /// - file: The file in which failure occurred. Defaults to the file name of the test case in which this function was called.
126+ /// - testName: The name of the test in which failure occurred. Defaults to the function name of the test case in which this function was called.
127+ /// - line: The line number on which failure occurred. Defaults to the line number on which this function was called.
128+ /// - Returns: A failure message or, if the value matches, nil.
129+ public func verifySnapshot< Value, Format> (
130+ matching value: @autoclosure ( ) throws -> Value ,
131+ as snapshotting: Snapshotting < Value , Format > ,
132+ named name: String ? = nil ,
133+ record recording: Bool = false ,
134+ timeout: TimeInterval = 5 ,
135+ file: StaticString = #file,
136+ testName: String = #function,
137+ line: UInt = #line
138+ )
139+ -> String ? {
140+
141+ let recording = recording || self . record
142+
143+ do {
144+ let fileUrl = URL ( fileURLWithPath: " \( file) " )
145+ let fileName = fileUrl. deletingPathExtension ( ) . lastPathComponent
146+ let directoryUrl = fileUrl. deletingLastPathComponent ( )
147+ let snapshotDirectoryUrl : URL = directoryUrl
148+ . appendingPathComponent ( " __Snapshots__ " )
149+ . appendingPathComponent ( fileName)
150+
151+ let identifier : String
152+ if let name = name {
153+ identifier = sanitizePathComponent ( name)
154+ } else {
155+ identifier = String ( counter)
156+ counter += 1
157+ }
90158
91- \( diffMessage)
92- """
93- : """
94- No reference was found on disk. Automatically recorded snapshot: …
159+ let testName = sanitizePathComponent ( testName)
160+ let snapshotFileUrl = snapshotDirectoryUrl
161+ . appendingPathComponent ( " \( testName) . \( identifier) " )
162+ . appendingPathExtension ( snapshotting. pathExtension ?? " " )
163+ let fileManager = FileManager . default
164+ try fileManager. createDirectory ( at: snapshotDirectoryUrl, withIntermediateDirectories: true )
165+
166+ let tookSnapshot = self . expectation ( description: " Took snapshot " )
167+ var optionalDiffable : Format ?
168+ snapshotting. snapshot ( try value ( ) ) . run { b in
169+ optionalDiffable = b
170+ tookSnapshot. fulfill ( )
171+ }
172+ #if os(Linux)
173+ self . waitForExpectations ( timeout: timeout)
174+ #else
175+ self . wait ( for: [ tookSnapshot] , timeout: timeout)
176+ #endif
95177
96- open " \( snapshotFileUrl. path) "
178+ guard let diffable = optionalDiffable else {
179+ return " Couldn't snapshot value "
180+ }
97181
98- Re-run " \( testName) " to test against the newly-recorded snapshot.
99- """
182+ guard !recording, fileManager. fileExists ( atPath: snapshotFileUrl. path) else {
183+ let diffMessage = ( try ? Data ( contentsOf: snapshotFileUrl) )
184+ . flatMap { data in snapshotting. diffing. diff ( snapshotting. diffing. fromData ( data) , diffable) }
185+ . map { diff, _ in diff. trimmingCharacters ( in: . whitespacesAndNewlines) }
186+ ?? " Recorded snapshot: … "
100187
101- XCTFail ( message, file: file, line: line)
102- return
103- }
188+ try snapshotting. diffing. toData ( diffable) . write ( to: snapshotFileUrl)
189+ return recording
190+ ? """
191+ Record mode is on. Turn record mode off and re-run " \( testName) " to test against the newly-recorded snapshot.
104192
105- let data = try Data ( contentsOf: snapshotFileUrl)
106- let reference = snapshotting. diffing. fromData ( data)
193+ open " \( snapshotFileUrl. path) "
107194
108- guard let ( failure, attachments) = snapshotting. diffing. diff ( reference, diffable) else {
109- return
110- }
195+ \( diffMessage)
196+ """
197+ : """
198+ No reference was found on disk. Automatically recorded snapshot: …
111199
112- let artifactsUrl = URL (
113- fileURLWithPath: ProcessInfo . processInfo. environment [ " SNAPSHOT_ARTIFACTS " ] ?? NSTemporaryDirectory ( )
114- )
115- let artifactsSubUrl = artifactsUrl. appendingPathComponent ( fileName)
116- try fileManager. createDirectory ( at: artifactsSubUrl, withIntermediateDirectories: true )
117- let failedSnapshotFileUrl = artifactsSubUrl. appendingPathComponent ( snapshotFileUrl. lastPathComponent)
118- try snapshotting. diffing. toData ( diffable) . write ( to: failedSnapshotFileUrl)
119-
120- if !attachments. isEmpty {
121- #if !os(Linux)
122- XCTContext . runActivity ( named: " Attached Failure Diff " ) { activity in
123- attachments. forEach {
124- activity. add ( $0)
200+ open " \( snapshotFileUrl. path) "
201+
202+ Re-run " \( testName) " to test against the newly-recorded snapshot.
203+ """
204+ }
205+
206+ let data = try Data ( contentsOf: snapshotFileUrl)
207+ let reference = snapshotting. diffing. fromData ( data)
208+
209+ guard let ( failure, attachments) = snapshotting. diffing. diff ( reference, diffable) else {
210+ return nil
211+ }
212+
213+ let artifactsUrl = URL (
214+ fileURLWithPath: ProcessInfo . processInfo. environment [ " SNAPSHOT_ARTIFACTS " ] ?? NSTemporaryDirectory ( )
215+ )
216+ let artifactsSubUrl = artifactsUrl. appendingPathComponent ( fileName)
217+ try fileManager. createDirectory ( at: artifactsSubUrl, withIntermediateDirectories: true )
218+ let failedSnapshotFileUrl = artifactsSubUrl. appendingPathComponent ( snapshotFileUrl. lastPathComponent)
219+ try snapshotting. diffing. toData ( diffable) . write ( to: failedSnapshotFileUrl)
220+
221+ if !attachments. isEmpty {
222+ #if !os(Linux)
223+ XCTContext . runActivity ( named: " Attached Failure Diff " ) { activity in
224+ attachments. forEach {
225+ activity. add ( $0)
226+ }
125227 }
228+ #endif
126229 }
127- #endif
128- }
129230
130- let diffMessage = self . diffTool
131- . map { " \( $0) \" \( snapshotFileUrl. path) \" \" \( failedSnapshotFileUrl. path) \" " }
132- ?? " @ \( minus) \n \" \( snapshotFileUrl. path) \" \n @ \( plus) \n \" \( failedSnapshotFileUrl. path) \" "
133- let message = """
134- Snapshot does not match reference.
231+ let diffMessage = self . diffTool
232+ . map { " \( $0) \" \( snapshotFileUrl. path) \" \" \( failedSnapshotFileUrl. path) \" " }
233+ ?? " @ \( minus) \n \" \( snapshotFileUrl. path) \" \n @ \( plus) \n \" \( failedSnapshotFileUrl. path) \" "
234+ let message = """
235+ Snapshot does not match reference.
135236
136- \( diffMessage)
237+ \( diffMessage)
137238
138- \( failure. trimmingCharacters ( in: . whitespacesAndNewlines) )
139- """
140- XCTFail ( message , file : file , line : line )
141- } catch {
142- XCTFail ( error . localizedDescription , file : file , line : line )
143- }
239+ \( failure. trimmingCharacters ( in: . whitespacesAndNewlines) )
240+ """
241+ return message
242+ } catch {
243+ return error . localizedDescription
244+ }
144245 }
145246
146247 private var counter = 1
0 commit comments