Skip to content

Commit

Permalink
Merge pull request #310 from Ben-G/benji/new-havecount-message
Browse files Browse the repository at this point in the history
[HaveCountMatcher] Implement Shorter Failure Message
  • Loading branch information
briancroom authored Jul 5, 2016
2 parents b4a0f9d + 436328a commit de76ce8
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 22 deletions.
10 changes: 9 additions & 1 deletion Sources/Nimble/FailureMessage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)"
}
Expand Down
11 changes: 9 additions & 2 deletions Sources/Nimble/Matchers/HaveCount.swift
Original file line number Diff line number Diff line change
@@ -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<T: CollectionType>(expectedValue: T.Index.Distance) -> NonNilMatcherFunc<T> {
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
Expand All @@ -20,9 +26,10 @@ public func haveCount<T: CollectionType>(expectedValue: T.Index.Distance) -> Non
public func haveCount(expectedValue: Int) -> MatcherFunc<NMBCollection> {
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
Expand Down
46 changes: 46 additions & 0 deletions Sources/Nimble/Utils/Stringers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,49 @@ public func stringify<T>(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<T>(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<T: CollectionType>(collection: T) -> String {
return String(collection.dynamicType)
}
21 changes: 12 additions & 9 deletions Tests/Nimble/Matchers/HaveCountTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Int> 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<Int> with count 3, got 3\nActual Value: [1, 2, 3]") {
expect([1, 2, 3]).notTo(haveCount(3))
}
}
Expand All @@ -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<String, Int> 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<String, Int> with count 3, got 3" +
"\nActual Value: \(stringify(dictionary))") {
expect(dictionary).notTo(haveCount(3))
}
}

Expand All @@ -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<Int> 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<Int> with count 3, got 3" +
"\nActual Value: \(stringify(set))") {
expect(set).notTo(haveCount(3))
}
}
}
20 changes: 10 additions & 10 deletions Tests/Nimble/objc/ObjCHaveCount.m
Original file line number Diff line number Diff line change
Expand Up @@ -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));
});

Expand All @@ -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));
});
}
Expand All @@ -47,15 +47,15 @@ - (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));
});


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));
Expand All @@ -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));
});
}
Expand All @@ -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));
});
}
Expand Down

0 comments on commit de76ce8

Please sign in to comment.