diff --git a/Sources/Difference.swift b/Sources/Difference.swift index ab920c7..9aade27 100644 --- a/Sources/Difference.swift +++ b/Sources/Difference.swift @@ -10,88 +10,182 @@ import Foundation private typealias IndentationType = Difference.IndentationType -fileprivate func diffLines(_ expected: T, _ received: T, level: Int = 0) -> [Line] { - let expectedMirror = Mirror(reflecting: expected) - let receivedMirror = Mirror(reflecting: received) +private struct Differ { + private let indentationType: IndentationType + private let skipPrintingOnDiffCount: Bool - guard expectedMirror.children.count != 0, receivedMirror.children.count != 0 else { - if String(dumping: received) != String(dumping: expected) { - return handleChildless(expected, expectedMirror, received, receivedMirror, level) - } - return [] + init( + indentationType: IndentationType, + skipPrintingOnDiffCount: Bool + ) { + self.indentationType = indentationType + self.skipPrintingOnDiffCount = skipPrintingOnDiffCount } - let hasDiffNumOfChildren = expectedMirror.children.count != receivedMirror.children.count - switch (expectedMirror.displayStyle, receivedMirror.displayStyle) { - case (.collection?, .collection?) where hasDiffNumOfChildren, - (.dictionary?, .dictionary?) where hasDiffNumOfChildren, - (.set?, .set?) where hasDiffNumOfChildren, - (.enum?, .enum?) where hasDiffNumOfChildren: - return [generateDifferentCountBlock(expected, expectedMirror, received, receivedMirror, level)] - case (.dictionary?, .dictionary?): - if let expectedDict = expected as? Dictionary, - let receivedDict = received as? Dictionary { - var resultLines: [Line] = [] - expectedDict.keys.forEach { key in - let results = diffLines(expectedDict[key], receivedDict[key], level: level + 1) - if !results.isEmpty { - resultLines.append(Line(contents: "Key \(key.description):", indentationLevel: level, canBeOrdered: true, children: results)) - } + func diff(_ expected: T, _ received: T) -> [String] { + let lines = diffLines(expected, received, level: 0) + return buildLineContents(lines: lines) + } + + fileprivate func diffLines(_ expected: T, _ received: T, level: Int = 0) -> [Line] { + let expectedMirror = Mirror(reflecting: expected) + let receivedMirror = Mirror(reflecting: received) + + guard expectedMirror.children.count != 0, receivedMirror.children.count != 0 else { + if String(dumping: received) != String(dumping: expected) { + return handleChildless(expected, expectedMirror, received, receivedMirror, level) } - return resultLines + return [] } - case (.set?, .set?): - if let expectedSet = expected as? Set, - let receivedSet = received as? Set { - return expectedSet.subtracting(receivedSet) - .map { unique in - Line(contents: "Missing: \(unique.description)", indentationLevel: level, canBeOrdered: true) + + let hasDiffNumOfChildren = expectedMirror.children.count != receivedMirror.children.count + switch (expectedMirror.displayStyle, receivedMirror.displayStyle) { + case (.collection?, .collection?) where hasDiffNumOfChildren, + (.dictionary?, .dictionary?) where hasDiffNumOfChildren, + (.set?, .set?) where hasDiffNumOfChildren, + (.enum?, .enum?) where hasDiffNumOfChildren: + return [generateDifferentCountBlock(expected, expectedMirror, received, receivedMirror, level)] + case (.dictionary?, .dictionary?): + if let expectedDict = expected as? Dictionary, + let receivedDict = received as? Dictionary { + var resultLines: [Line] = [] + expectedDict.keys.forEach { key in + let results = diffLines(expectedDict[key], receivedDict[key], level: level + 1) + if !results.isEmpty { + resultLines.append(Line(contents: "Key \(key.description):", indentationLevel: level, canBeOrdered: true, children: results)) + } } + return resultLines + } + case (.set?, .set?): + if let expectedSet = expected as? Set, + let receivedSet = received as? Set { + return expectedSet.subtracting(receivedSet) + .map { unique in + Line(contents: "Missing: \(unique.description)", indentationLevel: level, canBeOrdered: true) + } + } + // Handles different enum cases that have children to prevent printing entire object + case (.enum?, .enum?) where expectedMirror.children.first?.label != receivedMirror.children.first?.label: + let expectedPrintable = enumLabelFromFirstChild(expectedMirror) ?? "UNKNOWN" + let receivedPrintable = enumLabelFromFirstChild(receivedMirror) ?? "UNKNOWN" + return generateExpectedReceiveLines(expectedPrintable, receivedPrintable, level) + default: + break } - // Handles different enum cases that have children to prevent printing entire object - case (.enum?, .enum?) where expectedMirror.children.first?.label != receivedMirror.children.first?.label: - let expectedPrintable = enumLabelFromFirstChild(expectedMirror) ?? "UNKNOWN" - let receivedPrintable = enumLabelFromFirstChild(receivedMirror) ?? "UNKNOWN" - return generateExpectedReceiveLines(expectedPrintable, receivedPrintable, level) - default: - break - } - var resultLines = [Line]() - let zipped = zip(expectedMirror.children, receivedMirror.children) - zipped.enumerated().forEach { (index, zippedValues) in - let lhs = zippedValues.0 - let rhs = zippedValues.1 - let leftDump = String(dumping: lhs.value) - if leftDump != String(dumping: rhs.value) { - // Remove embedding of `some` for optional types, as it offers no value - guard expectedMirror.displayStyle != .optional else { - let results = diffLines(lhs.value, rhs.value, level: level) - resultLines.append(contentsOf: results) - return - } - if Mirror(reflecting: lhs.value).displayStyle != nil { - let results = diffLines(lhs.value, rhs.value, level: level + 1) - if !results.isEmpty { - let line = Line(contents: "\(expectedMirror.displayStyleDescriptor(index: index))\(lhs.label ?? ""):", - indentationLevel: level, - canBeOrdered: true, - children: results + var resultLines = [Line]() + let zipped = zip(expectedMirror.children, receivedMirror.children) + zipped.enumerated().forEach { (index, zippedValues) in + let lhs = zippedValues.0 + let rhs = zippedValues.1 + let leftDump = String(dumping: lhs.value) + if leftDump != String(dumping: rhs.value) { + // Remove embedding of `some` for optional types, as it offers no value + guard expectedMirror.displayStyle != .optional else { + let results = diffLines(lhs.value, rhs.value, level: level) + resultLines.append(contentsOf: results) + return + } + if Mirror(reflecting: lhs.value).displayStyle != nil { + let results = diffLines(lhs.value, rhs.value, level: level + 1) + if !results.isEmpty { + let line = Line(contents: "\(expectedMirror.displayStyleDescriptor(index: index))\(lhs.label ?? ""):", + indentationLevel: level, + canBeOrdered: true, + children: results + ) + resultLines.append(line) + } + } else { + let childName = "\(expectedMirror.displayStyleDescriptor(index: index))\(lhs.label ?? ""):" + let children = generateExpectedReceiveLines( + String(describing: lhs.value), + String(describing: rhs.value), + level + 1 ) - resultLines.append(line) + resultLines.append(Line(contents: childName, indentationLevel: level, canBeOrdered: true, children: children)) } - } else { - let childName = "\(expectedMirror.displayStyleDescriptor(index: index))\(lhs.label ?? ""):" - let children = generateExpectedReceiveLines( - String(describing: lhs.value), - String(describing: rhs.value), - level + 1 - ) - resultLines.append(Line(contents: childName, indentationLevel: level, canBeOrdered: true, children: children)) } } + return resultLines + } + + + fileprivate func handleChildless( + _ expected: T, + _ expectedMirror: Mirror, + _ received: T, + _ receivedMirror: Mirror, + _ indentationLevel: Int + ) -> [Line] { + // Empty collections are "childless", so we may need to generate a different count block instead of treating as a + // childless enum. + guard !expectedMirror.canBeEmpty else { + return [generateDifferentCountBlock(expected, expectedMirror, received, receivedMirror, indentationLevel)] + } + + let receivedPrintable: String + let expectedPrintable: String + // Received mirror has a different number of arguments to expected + if receivedMirror.children.count == 0, expectedMirror.children.count != 0 { + // Print whole description of received, as it's only a label if childless + receivedPrintable = String(dumping: received) + // Get the label from the expected, to prevent printing long list of arguments + expectedPrintable = enumLabelFromFirstChild(expectedMirror) ?? String(describing: expected) + } else if expectedMirror.children.count == 0, receivedMirror.children.count != 0 { + receivedPrintable = enumLabelFromFirstChild(receivedMirror) ?? String(describing: received) + expectedPrintable = String(dumping: expected) + } else { + receivedPrintable = String(describing: received) + expectedPrintable = String(describing: expected) + } + return generateExpectedReceiveLines(expectedPrintable, receivedPrintable, indentationLevel) + } + + private func generateDifferentCountBlock( + _ expected: T, + _ expectedMirror: Mirror, + _ received: T, + _ receivedMirror: Mirror, + _ indentationLevel: Int + ) -> Line { + var expectedPrintable = "(\(expectedMirror.children.count))" + var receivedPrintable = "(\(receivedMirror.children.count))" + if !skipPrintingOnDiffCount { + expectedPrintable.append(" \(expected)") + receivedPrintable.append(" \(received)") + } + return Line( + contents: "Different count:", + indentationLevel: indentationLevel, + canBeOrdered: false, + children: generateExpectedReceiveLines(expectedPrintable, receivedPrintable, indentationLevel + 1) + ) + } + + private func generateExpectedReceiveLines( + _ expected: String, + _ received: String, + _ indentationLevel: Int + ) -> [Line] { + return [ + Line(contents: "Received: \(received)", indentationLevel: indentationLevel, canBeOrdered: false), + Line(contents: "Expected: \(expected)", indentationLevel: indentationLevel, canBeOrdered: false) + ] + } + + private func buildLineContents(lines: [Line]) -> [String] { + let linesContents = lines.map { line in line.generateContents(indentationType: indentationType) } + // In the case of this being a top level failure (e.g. both mirrors have no children, like comparing two + // primitives `diff(2,3)`, we only want to produce one failure to have proper spacing. + let isOnlyTopLevelFailure = lines.map { $0.hasChildren }.filter { $0 }.isEmpty + if isOnlyTopLevelFailure { + return [linesContents.joined()] + } else { + return linesContents + } } - return resultLines } public enum Difference { @@ -114,7 +208,7 @@ public enum Difference { /// counter: /// Received: 1 /// Expected: 2 - public enum IndentationType: String { + public enum IndentationType: String, CaseIterable { case pipe = "|\t" case tab = "\t" } @@ -157,65 +251,6 @@ private struct Line { } } -fileprivate func handleChildless( - _ expected: T, - _ expectedMirror: Mirror, - _ received: T, - _ receivedMirror: Mirror, - _ indentationLevel: Int -) -> [Line] { - // Empty collections are "childless", so we may need to generate a different count block instead of treating as a - // childless enum. - guard !expectedMirror.canBeEmpty else { - return [generateDifferentCountBlock(expected, expectedMirror, received, receivedMirror, indentationLevel)] - } - - let receivedPrintable: String - let expectedPrintable: String - // Received mirror has a different number of arguments to expected - if receivedMirror.children.count == 0, expectedMirror.children.count != 0 { - // Print whole description of received, as it's only a label if childless - receivedPrintable = String(dumping: received) - // Get the label from the expected, to prevent printing long list of arguments - expectedPrintable = enumLabelFromFirstChild(expectedMirror) ?? String(describing: expected) - } else if expectedMirror.children.count == 0, receivedMirror.children.count != 0 { - receivedPrintable = enumLabelFromFirstChild(receivedMirror) ?? String(describing: received) - expectedPrintable = String(dumping: expected) - } else { - receivedPrintable = String(describing: received) - expectedPrintable = String(describing: expected) - } - return generateExpectedReceiveLines(expectedPrintable, receivedPrintable, indentationLevel) -} - -private func generateDifferentCountBlock( - _ expected: T, - _ expectedMirror: Mirror, - _ received: T, - _ receivedMirror: Mirror, - _ indentationLevel: Int -) -> Line { - let expectedPrintable = "(\(expectedMirror.children.count)) \(expected)" - let receivedPrintable = "(\(receivedMirror.children.count)) \(received)" - return Line( - contents: "Different count:", - indentationLevel: indentationLevel, - canBeOrdered: false, - children: generateExpectedReceiveLines(expectedPrintable, receivedPrintable, indentationLevel + 1) - ) -} - -private func generateExpectedReceiveLines( - _ expected: String, - _ received: String, - _ indentationLevel: Int -) -> [Line] { - return [ - Line(contents: "Received: \(received)", indentationLevel: indentationLevel, canBeOrdered: false), - Line(contents: "Expected: \(expected)", indentationLevel: indentationLevel, canBeOrdered: false) - ] -} - fileprivate extension String { init(dumping object: T) { self.init() @@ -267,22 +302,18 @@ fileprivate extension Mirror { /// - Parameters: /// - expected: Expected value /// - received: Received value +/// - indentationType: Style of indentation to use +/// - skipPrintingOnDiffCount: Skips the printing of the object when a collection has a different count +/// /// - Returns: List of differences public func diff( _ expected: T, _ received: T, - indentationType: Difference.IndentationType = .pipe + indentationType: Difference.IndentationType = .pipe, + skipPrintingOnDiffCount: Bool = false ) -> [String] { - let lines = diffLines(expected, received) - let linesContents = lines.map { line in line.generateContents(indentationType: indentationType) } - // In the case of this being a top level failure (e.g. both mirrors have no children, like comparing two - // primitives `diff(2,3)`, we only want to produce one failure to have proper spacing. - let isOnlyTopLevelFailure = lines.map { $0.hasChildren }.filter { $0 }.isEmpty - if isOnlyTopLevelFailure { - return [linesContents.joined()] - } else { - return linesContents - } + Differ(indentationType: indentationType, skipPrintingOnDiffCount: skipPrintingOnDiffCount) + .diff(expected, received) } /// Prints list of differences between 2 objects @@ -290,17 +321,25 @@ public func diff( /// - Parameters: /// - expected: Expected value /// - received: Received value +/// - indentationType: Style of indentation to use +/// - skipPrintingOnDiffCount: Skips the printing of the object when a collection has a different count public func dumpDiff( _ expected: T, _ received: T, - indentationType: Difference.IndentationType = .pipe + indentationType: Difference.IndentationType = .pipe, + skipPrintingOnDiffCount: Bool = false ) { // skip equal guard expected != received else { return } - diff(expected, received, indentationType: indentationType).forEach { print($0) } + diff( + expected, + received, + indentationType: indentationType, + skipPrintingOnDiffCount: skipPrintingOnDiffCount + ).forEach { print($0) } } @@ -309,10 +348,18 @@ public func dumpDiff( /// - Parameters: /// - expected: Expected value /// - received: Received value +/// - indentationType: Style of indentation to use +/// - skipPrintingOnDiffCount: Skips the printing of the object when a collection has a different count public func dumpDiff( _ expected: T, _ received: T, - indentationType: Difference.IndentationType = .pipe + indentationType: Difference.IndentationType = .pipe, + skipPrintingOnDiffCount: Bool = false ) { - diff(expected, received, indentationType: indentationType).forEach { print($0) } + diff( + expected, + received, + indentationType: indentationType, + skipPrintingOnDiffCount: skipPrintingOnDiffCount + ).forEach { print($0) } } diff --git a/Tests/DifferenceTests/DifferenceTests.swift b/Tests/DifferenceTests/DifferenceTests.swift index 76b8b98..5fbdc5e 100644 --- a/Tests/DifferenceTests/DifferenceTests.swift +++ b/Tests/DifferenceTests/DifferenceTests.swift @@ -10,6 +10,8 @@ import Foundation import XCTest import Difference +typealias IndentationType = Difference.IndentationType + fileprivate struct Person: Equatable { let name: String let age: Int @@ -74,163 +76,187 @@ private enum State { case loadedWithNoArguments } +extension String { + func adjustingFor(indentationType: IndentationType) -> String { + switch indentationType { + case .pipe: + return self + case .tab: + return self.replacingOccurrences(of: "|", with: "") + } + } +} + class DifferenceTests: XCTestCase { - func testCanFindRootPrimitiveDifference() { - let results = diff(2, 3) - XCTAssertEqual(results.count, 1) - XCTAssertEqual(results.first, "Received: 3\nExpected: 2\n") + private func runTest( + expected: T, + received: T, + expectedResults: [String], + skipPrintingOnDiffCount: Bool = false + ) { + IndentationType.allCases.forEach { indentationType in + let results = diff(expected, received, indentationType: indentationType, skipPrintingOnDiffCount: skipPrintingOnDiffCount) + let preppedExpected = expectedResults.map { $0.adjustingFor(indentationType: indentationType) } + XCTAssertEqual(results.count, expectedResults.count) + XCTAssertEqual(results, preppedExpected) + } + } + + func testCanFindRootPrimitiveDifference() { + runTest( + expected: 2, + received: 3, + expectedResults: ["Received: 3\nExpected: 2\n"] + ) } fileprivate let truth = Person() func testCanFindPrimitiveDifference() { - let stub = Person(age: 30) - let results = diff(truth, stub) - - XCTAssertEqual(results.count, 1) - XCTAssertEqual(results.first, "age:\n|\tReceived: 30\n|\tExpected: 29\n") - + runTest( + expected: truth, + received: Person(age: 30), + expectedResults: ["age:\n|\tReceived: 30\n|\tExpected: 29\n"] + ) } func testCanFindMultipleDifference() { - let stub = Person(name: "Adam", age: 30) - let results = diff(truth, stub) - - XCTAssertEqual(results.count, 2) - XCTAssertEqual(results.first, "name:\n|\tReceived: Adam\n|\tExpected: Krzysztof\n") - XCTAssertEqual(results.last, "age:\n|\tReceived: 30\n|\tExpected: 29\n") + runTest( + expected: truth, + received: Person(name: "Adam", age: 30), + expectedResults: [ + "name:\n|\tReceived: Adam\n|\tExpected: Krzysztof\n", + "age:\n|\tReceived: 30\n|\tExpected: 29\n" + ] + ) } func testCanFindComplexDifference() { - let stub = Person(address: Person.Address(street: "2nd Street", counter: .init(counter: 1))) - let results = diff(truth, stub) - - XCTAssertEqual(results.count, 1) - XCTAssertEqual(results.first, "address:\n|\tcounter:\n|\t|\tcounter:\n|\t|\t|\tReceived: 1\n|\t|\t|\tExpected: 2\n|\tstreet:\n|\t|\tReceived: 2nd Street\n|\t|\tExpected: Times Square\n") - + runTest( + expected: truth, + received: Person(address: Person.Address(street: "2nd Street", counter: .init(counter: 1))), + expectedResults: ["address:\n|\tcounter:\n|\t|\tcounter:\n|\t|\t|\tReceived: 1\n|\t|\t|\tExpected: 2\n|\tstreet:\n|\t|\tReceived: 2nd Street\n|\t|\tExpected: Times Square\n"] + ) } func testCanGiveDescriptionForOptionalOnLeftSide() { - let truth = Person(pet: nil) - let stub = Person() - let results = diff(truth, stub) - + let results = diff(Person(pet: nil), Person()) XCTAssertEqual(results.count, 1) } func testCanGiveDescriptionForOptionalOnRightSide() { - let truth = Person() - let stub = Person(pet: nil) - let results = diff(truth, stub) - + let results = diff(Person(), Person(pet: nil)) XCTAssertEqual(results.count, 1) } // MARK: Collections func test_canFindCollectionCountDifference() { - let results = diff([1], [1, 3]) - - XCTAssertEqual(results.count, 1) - XCTAssertEqual(results.first, "Different count:\n|\tReceived: (2) [1, 3]\n|\tExpected: (1) [1]\n") + runTest( + expected: [1], + received: [1, 3], + expectedResults: ["Different count:\n|\tReceived: (2) [1, 3]\n|\tExpected: (1) [1]\n"] + ) } func test_canFindCollectionCountDifference_complex() { - let truth = State.loaded([1, 2], "truthString") - let stub = State.loaded([], "stubString") - let results = diff(truth, stub) + runTest( + expected: State.loaded([1, 2], "truthString"), + received: State.loaded([], "stubString"), + expectedResults: ["Enum loaded:\n|\t.0:\n|\t|\tDifferent count:\n|\t|\t|\tReceived: (0) []\n|\t|\t|\tExpected: (2) [1, 2]\n|\t.1:\n|\t|\tReceived: stubString\n|\t|\tExpected: truthString\n"] + ) + } - XCTAssertEqual(results.count, 1) - XCTAssertEqual(results.first, "Enum loaded:\n|\t.0:\n|\t|\tDifferent count:\n|\t|\t|\tReceived: (0) []\n|\t|\t|\tExpected: (2) [1, 2]\n|\t.1:\n|\t|\tReceived: stubString\n|\t|\tExpected: truthString\n") + func test_collectionCountDifference_withoutPrintingObject() { + dumpDiff([1], [1, 3], indentationType: .pipe, skipPrintingOnDiffCount: true) + runTest( + expected: [1], + received: [1, 3], + expectedResults: ["Different count:\n|\tReceived: (2)\n|\tExpected: (1)\n"], + skipPrintingOnDiffCount: true + ) } func test_labelsArrayElementsInDiff() { - let truth = [Person(), Person(name: "John")] - let stub = [Person(name: "John"), Person()] - let results = diff(truth, stub) - - XCTAssertEqual(results.count, 2) - XCTAssertEqual(results.first, "Collection[0]:\n|\tname:\n|\t|\tReceived: John\n|\t|\tExpected: Krzysztof\n") - XCTAssertEqual(results.last, "Collection[1]:\n|\tname:\n|\t|\tReceived: Krzysztof\n|\t|\tExpected: John\n") + runTest( + expected: [Person(), Person(name: "John")], + received: [Person(name: "John"), Person()], + expectedResults: [ + "Collection[0]:\n|\tname:\n|\t|\tReceived: John\n|\t|\tExpected: Krzysztof\n", + "Collection[1]:\n|\tname:\n|\t|\tReceived: Krzysztof\n|\t|\tExpected: John\n" + ] + ) } // MARK: Enums func test_canFindEnumCaseDifferenceWhenAssociatedValuesAreIdentical() { - let truth = State.loaded([0], "CommonString") - let stub = State.anotherLoaded([0], "CommonString") - let results = diff(truth, stub) - - XCTAssertEqual(results.count, 1) - XCTAssertEqual(results.first, "Received: anotherLoaded\nExpected: loaded\n") + runTest( + expected: State.loaded([0], "CommonString"), + received: State.anotherLoaded([0], "CommonString"), + expectedResults: ["Received: anotherLoaded\nExpected: loaded\n"] + ) } func test_canFindEnumCaseDifferenceWhenLessArguments() { - let truth = State.loaded([0], "CommonString") - let stub = State.loadedWithDiffArguments(1) - let results = diff(truth, stub) - - XCTAssertEqual(results.count, 1) - XCTAssertEqual(results.first, "Received: loadedWithDiffArguments\nExpected: loaded\n") + runTest( + expected: State.loaded([0], "CommonString"), + received: State.loadedWithDiffArguments(1), + expectedResults: ["Received: loadedWithDiffArguments\nExpected: loaded\n"] + ) } // MARK: Dictionaries func test_canFindDictionaryCountDifference() { - let truth = Person(petAges: ["Henny": 4]) - let stub = Person(petAges: [:]) - let results = diff(truth, stub) - - XCTAssertEqual(results.count, 1) - XCTAssertEqual(results.first, "petAges:\n|\tDifferent count:\n|\t|\tReceived: (0) [:]\n|\t|\tExpected: (1) [\"Henny\": 4]\n") + runTest( + expected: Person(petAges: ["Henny": 4]), + received: Person(petAges: [:]), + expectedResults: ["petAges:\n|\tDifferent count:\n|\t|\tReceived: (0) [:]\n|\t|\tExpected: (1) [\"Henny\": 4]\n"] + ) } func test_canFindOptionalDifferenceBetweenSomeAndNone() { - let truth = Person(petAges: ["Henny": 4]) - let stub = Person(petAges: nil) - let results = diff(truth, stub) - - XCTAssertEqual(results.count, 1) - XCTAssertEqual(results.first, "petAges:\n|\tReceived: nil\n|\tExpected: Optional([\"Henny\": 4])\n") + runTest( + expected: Person(petAges: ["Henny": 4]), + received: Person(petAges: nil), + expectedResults: ["petAges:\n|\tReceived: nil\n|\tExpected: Optional([\"Henny\": 4])\n"] + ) } func test_canFindDictionaryDifference() { - let truth = Person(petAges: ["Henny": 4, "Jethro": 6]) - let stub = Person(petAges: ["Henny": 1, "Jethro": 2]) - let results = diff(truth, stub) - - XCTAssertEqual(results.count, 1) - XCTAssertEqual(results.first, "petAges:\n|\tKey Henny:\n|\t|\tReceived: 1\n|\t|\tExpected: 4\n|\tKey Jethro:\n|\t|\tReceived: 2\n|\t|\tExpected: 6\n") + runTest( + expected: Person(petAges: ["Henny": 4, "Jethro": 6]), + received: Person(petAges: ["Henny": 1, "Jethro": 2]), + expectedResults: ["petAges:\n|\tKey Henny:\n|\t|\tReceived: 1\n|\t|\tExpected: 4\n|\tKey Jethro:\n|\t|\tReceived: 2\n|\t|\tExpected: 6\n"] + ) } // MARK: Sets func test_canFindSetCountDifference() { - let truth = Person(favoriteFoods: []) - let stub = Person(favoriteFoods: ["Oysters"]) - let results = diff(truth, stub) - - XCTAssertEqual(results.count, 1) - XCTAssertEqual(results.first, "favoriteFoods:\n|\tDifferent count:\n|\t|\tReceived: (1) [\"Oysters\"]\n|\t|\tExpected: (0) []\n") + runTest( + expected: Person(favoriteFoods: []), + received: Person(favoriteFoods: ["Oysters"]), + expectedResults: ["favoriteFoods:\n|\tDifferent count:\n|\t|\tReceived: (1) [\"Oysters\"]\n|\t|\tExpected: (0) []\n"] + ) } func test_canFindOptionalSetDifferenceBetweenSomeAndNone() { - let truth = Person(favoriteFoods: ["Oysters"]) - let stub = Person(favoriteFoods: nil) - let results = diff(truth, stub) - - XCTAssertEqual(results.count, 1) - XCTAssertEqual(results.first, "favoriteFoods:\n|\tReceived: nil\n|\tExpected: Optional(Set([\"Oysters\"]))\n") + runTest( + expected: Person(favoriteFoods: ["Oysters"]), + received: Person(favoriteFoods: nil), + expectedResults: ["favoriteFoods:\n|\tReceived: nil\n|\tExpected: Optional(Set([\"Oysters\"]))\n"] + ) } func test_canFindSetDifference() { - let truth = Person(favoriteFoods: ["Sushi", "Pizza"]) - let stub = Person(favoriteFoods: ["Oysters", "Crab"]) - let results = diff(truth, stub) - - XCTAssertEqual(results.count, 1) - XCTAssertEqual(results.first, "favoriteFoods:\n|\tMissing: Pizza\n|\tMissing: Sushi\n") + runTest( + expected: Person(favoriteFoods: ["Sushi", "Pizza"]), + received: Person(favoriteFoods: ["Oysters", "Crab"]), + expectedResults: ["favoriteFoods:\n|\tMissing: Pizza\n|\tMissing: Sushi\n"] + ) } }