diff --git a/Sources/Nimble/FailureMessage.swift b/Sources/Nimble/FailureMessage.swift index 4d23bc8e8..537d1bbc9 100644 --- a/Sources/Nimble/FailureMessage.swift +++ b/Sources/Nimble/FailureMessage.swift @@ -9,6 +9,10 @@ public class FailureMessage: NSObject { public var to: String = "to" public var postfixMessage: String = "match" public var postfixActual: String = "" + /// An optional message that will be appended as a new line and provides additional details + /// about the failure. This message will only be visible in the issue navigator / in logs but + /// not directly in the source editor since only a single line is presented there. + public var extendedMessage: String? = nil public var userDescription: String? = nil public var stringValue: String { @@ -46,7 +50,11 @@ public class FailureMessage: NSObject { value = "\(expected) \(to) \(postfixMessage), got \(actualValue)\(postfixActual)" } value = stripNewlines(value) - + + if let extendedMessage = extendedMessage { + value += "\n\(stripNewlines(extendedMessage))" + } + if let userDescription = userDescription { return "\(userDescription)\n\(value)" } diff --git a/Sources/Nimble/Matchers/HaveCount.swift b/Sources/Nimble/Matchers/HaveCount.swift index a17cca2d2..203bb4b01 100644 --- a/Sources/Nimble/Matchers/HaveCount.swift +++ b/Sources/Nimble/Matchers/HaveCount.swift @@ -1,13 +1,19 @@ import Foundation +// The `haveCount` matchers do not print the full string representation of the collection value, +// instead they only print the type name and the expected count. This makes it easier to understand +// the reason for failed expectations. See: https://github.com/Quick/Nimble/issues/308. +// The representation of the collection content is provided in a new line as an `extendedMessage`. + /// A Nimble matcher that succeeds when the actual CollectionType's count equals /// the expected value public func haveCount(expectedValue: T.Index.Distance) -> NonNilMatcherFunc { return NonNilMatcherFunc { actualExpression, failureMessage in if let actualValue = try actualExpression.evaluate() { - failureMessage.postfixMessage = "have \(stringify(actualValue)) with count \(stringify(expectedValue))" + failureMessage.postfixMessage = "have \(prettyCollectionType(actualValue)) with count \(stringify(expectedValue))" let result = expectedValue == actualValue.count failureMessage.actualValue = "\(actualValue.count)" + failureMessage.extendedMessage = "Actual Value: \(stringify(actualValue))" return result } else { return false @@ -20,9 +26,10 @@ public func haveCount(expectedValue: T.Index.Distance) -> Non public func haveCount(expectedValue: Int) -> MatcherFunc { return MatcherFunc { actualExpression, failureMessage in if let actualValue = try actualExpression.evaluate() { - failureMessage.postfixMessage = "have \(stringify(actualValue)) with count \(stringify(expectedValue))" + failureMessage.postfixMessage = "have \(prettyCollectionType(actualValue)) with count \(stringify(expectedValue))" let result = expectedValue == actualValue.count failureMessage.actualValue = "\(actualValue.count)" + failureMessage.extendedMessage = "Actual Value: \(stringify(actualValue))" return result } else { return false diff --git a/Sources/Nimble/Utils/Stringers.swift b/Sources/Nimble/Utils/Stringers.swift index 4edead3f4..9cb784bef 100644 --- a/Sources/Nimble/Utils/Stringers.swift +++ b/Sources/Nimble/Utils/Stringers.swift @@ -169,3 +169,49 @@ public func stringify(value: T?) -> String { } } #endif + +// MARK: Collection Type Stringers + +/// Attempts to generate a pretty type string for a given value. If the value is of a Objective-C +/// collection type, or a subclass thereof, (e.g. `NSArray`, `NSDictionary`, etc.). +/// This function will return the type name of the root class of the class cluster for better +/// readability (e.g. `NSArray` instead of `__NSArrayI`). +/// +/// For values that don't have a type of an Objective-C collection, this function returns the +/// default type description. +/// +/// - parameter value: A value that will be used to determine a type name. +/// +/// - returns: The name of the class cluster root class for Objective-C collection types, or the +/// the `dynamicType` of the value for values of any other type. +public func prettyCollectionType(value: T) -> String { + #if _runtime(_ObjC) + // Check for types that are not in corelibs-foundation separately + if value is NSHashTable { + return String(NSHashTable.self) + } + #endif + + switch value { + case is NSArray: + return String(NSArray.self) + case is NSDictionary: + return String(NSDictionary.self) + case is NSSet: + return String(NSSet.self) + case is NSIndexSet: + return String(NSIndexSet.self) + default: + return String(value) + } +} + +/// Returns the type name for a given collection type. This overload is used by Swift +/// collection types. +/// +/// - parameter collection: A Swift `CollectionType` value. +/// +/// - returns: A string representing the `dynamicType` of the value. +public func prettyCollectionType(collection: T) -> String { + return String(collection.dynamicType) +} diff --git a/Tests/Nimble/Matchers/HaveCountTest.swift b/Tests/Nimble/Matchers/HaveCountTest.swift index 9dd79d69b..8d97b8607 100644 --- a/Tests/Nimble/Matchers/HaveCountTest.swift +++ b/Tests/Nimble/Matchers/HaveCountTest.swift @@ -14,11 +14,11 @@ class HaveCountTest: XCTestCase, XCTestCaseProvider { expect([1, 2, 3]).to(haveCount(3)) expect([1, 2, 3]).notTo(haveCount(1)) - failsWithErrorMessage("expected to have [1, 2, 3] with count 1, got 3") { + failsWithErrorMessage("expected to have Array with count 1, got 3\nActual Value: [1, 2, 3]") { expect([1, 2, 3]).to(haveCount(1)) } - failsWithErrorMessage("expected to not have [1, 2, 3] with count 3, got 3") { + failsWithErrorMessage("expected to not have Array with count 3, got 3\nActual Value: [1, 2, 3]") { expect([1, 2, 3]).notTo(haveCount(3)) } } @@ -28,12 +28,13 @@ class HaveCountTest: XCTestCase, XCTestCaseProvider { expect(dictionary).to(haveCount(3)) expect(dictionary).notTo(haveCount(1)) - failsWithErrorMessage("expected to have \(stringify(dictionary)) with count 1, got 3") { + failsWithErrorMessage("expected to have Dictionary with count 1, got 3\nActual Value: \(stringify(dictionary))") { expect(dictionary).to(haveCount(1)) } - failsWithErrorMessage("expected to not have \(stringify(dictionary)) with count 3, got 3") { - expect(dictionary).notTo(haveCount(3)) + failsWithErrorMessage("expected to not have Dictionary with count 3, got 3" + + "\nActual Value: \(stringify(dictionary))") { + expect(dictionary).notTo(haveCount(3)) } } @@ -42,12 +43,14 @@ class HaveCountTest: XCTestCase, XCTestCaseProvider { expect(set).to(haveCount(3)) expect(set).notTo(haveCount(1)) - failsWithErrorMessage("expected to have \(stringify(set)) with count 1, got 3") { - expect(set).to(haveCount(1)) + failsWithErrorMessage("expected to have Set with count 1, got 3" + + "\nActual Value: \(stringify(set))") { + expect(set).to(haveCount(1)) } - failsWithErrorMessage("expected to not have \(stringify(set)) with count 3, got 3") { - expect(set).notTo(haveCount(3)) + failsWithErrorMessage("expected to not have Set with count 3, got 3" + + "\nActual Value: \(stringify(set))") { + expect(set).notTo(haveCount(3)) } } } diff --git a/Tests/Nimble/objc/ObjCHaveCount.m b/Tests/Nimble/objc/ObjCHaveCount.m index b057fc031..af97f7cf9 100644 --- a/Tests/Nimble/objc/ObjCHaveCount.m +++ b/Tests/Nimble/objc/ObjCHaveCount.m @@ -14,11 +14,11 @@ - (void)testHaveCountForNSArray { expect(@[]).to(haveCount(@0)); expect(@[@1]).notTo(haveCount(@0)); - expectFailureMessage(@"expected to have (1, 2, 3) with count 1, got 3", ^{ + expectFailureMessage(@"expected to have NSArray with count 1, got 3\nActual Value: (1, 2, 3)", ^{ expect(@[@1, @2, @3]).to(haveCount(@1)); }); - expectFailureMessage(@"expected to not have (1, 2, 3) with count 3, got 3", ^{ + expectFailureMessage(@"expected to not have NSArray with count 3, got 3\nActual Value: (1, 2, 3)", ^{ expect(@[@1, @2, @3]).notTo(haveCount(@3)); }); @@ -28,11 +28,11 @@ - (void)testHaveCountForNSDictionary { expect(@{@"1":@1, @"2":@2, @"3":@3}).to(haveCount(@3)); expect(@{@"1":@1, @"2":@2, @"3":@3}).notTo(haveCount(@1)); - expectFailureMessage(@"expected to have {1 = 1;2 = 2;3 = 3;} with count 1, got 3", ^{ + expectFailureMessage(@"expected to have NSDictionary with count 1, got 3\nActual Value: {1 = 1;2 = 2;3 = 3;}", ^{ expect(@{@"1":@1, @"2":@2, @"3":@3}).to(haveCount(@1)); }); - expectFailureMessage(@"expected to not have {1 = 1;2 = 2;3 = 3;} with count 3, got 3", ^{ + expectFailureMessage(@"expected to not have NSDictionary with count 3, got 3\nActual Value: {1 = 1;2 = 2;3 = 3;}", ^{ expect(@{@"1":@1, @"2":@2, @"3":@3}).notTo(haveCount(@3)); }); } @@ -47,7 +47,7 @@ - (void)testHaveCountForNSHashtable { expect(table).notTo(haveCount(@1)); NSString *msg = [NSString stringWithFormat: - @"expected to have %@with count 1, got 3", + @"expected to have NSHashTable with count 1, got 3\nActual Value: %@", [table.description stringByReplacingOccurrencesOfString:@"\n" withString:@""]]; expectFailureMessage(msg, ^{ expect(table).to(haveCount(@1)); @@ -55,7 +55,7 @@ - (void)testHaveCountForNSHashtable { msg = [NSString stringWithFormat: - @"expected to not have %@with count 3, got 3", + @"expected to not have NSHashTable with count 3, got 3\nActual Value: %@", [table.description stringByReplacingOccurrencesOfString:@"\n" withString:@""]]; expectFailureMessage(msg, ^{ expect(table).notTo(haveCount(@3)); @@ -68,11 +68,11 @@ - (void)testHaveCountForNSSet { expect(set).to(haveCount(@3)); expect(set).notTo(haveCount(@1)); - expectFailureMessage(@"expected to have {(3,1,2)} with count 1, got 3", ^{ + expectFailureMessage(@"expected to have NSSet with count 1, got 3\nActual Value: {(3,1,2)}", ^{ expect(set).to(haveCount(@1)); }); - expectFailureMessage(@"expected to not have {(3,1,2)} with count 3, got 3", ^{ + expectFailureMessage(@"expected to not have NSSet with count 3, got 3\nActual Value: {(3,1,2)}", ^{ expect(set).notTo(haveCount(@3)); }); } @@ -83,11 +83,11 @@ - (void)testHaveCountForNSIndexSet { expect(set).to(haveCount(@3)); expect(set).notTo(haveCount(@1)); - expectFailureMessage(@"expected to have (1, 2, 3) with count 1, got 3", ^{ + expectFailureMessage(@"expected to have NSIndexSet with count 1, got 3\nActual Value: (1, 2, 3)", ^{ expect(set).to(haveCount(@1)); }); - expectFailureMessage(@"expected to not have (1, 2, 3) with count 3, got 3", ^{ + expectFailureMessage(@"expected to not have NSIndexSet with count 3, got 3\nActual Value: (1, 2, 3)", ^{ expect(set).notTo(haveCount(@3)); }); }