diff --git a/Nimble/Expectation.swift b/Nimble/Expectation.swift index 1febba300..0362db56a 100644 --- a/Nimble/Expectation.swift +++ b/Nimble/Expectation.swift @@ -1,7 +1,8 @@ import Foundation -internal func expressionMatches(expression: Expression, matcher: U, to: String) -> (Bool, FailureMessage) { +internal func expressionMatches(expression: Expression, matcher: U, to: String, description: String?) -> (Bool, FailureMessage) { let msg = FailureMessage() + msg.userDescription = description msg.to = to let pass = matcher.matches(expression, failureMessage: msg) if msg.actualValue == "" { @@ -10,8 +11,9 @@ internal func expressionMatches(express return (pass, msg) } -internal func expressionDoesNotMatch(expression: Expression, matcher: U, toNot: String) -> (Bool, FailureMessage) { +internal func expressionDoesNotMatch(expression: Expression, matcher: U, toNot: String, description: String?) -> (Bool, FailureMessage) { let msg = FailureMessage() + msg.userDescription = description msg.to = toNot let pass = matcher.doesNotMatch(expression, failureMessage: msg) if msg.actualValue == "" { @@ -28,22 +30,22 @@ public struct Expectation { } /// Tests the actual value using a matcher to match. - public func to(matcher: U) { - let (pass, msg) = expressionMatches(expression, matcher: matcher, to: "to") + public func to(matcher: U, description: String? = nil) { + let (pass, msg) = expressionMatches(expression, matcher: matcher, to: "to", description: description) verify(pass, msg) } /// Tests the actual value using a matcher to not match. - public func toNot(matcher: U) { - let (pass, msg) = expressionDoesNotMatch(expression, matcher: matcher, toNot: "to not") + public func toNot(matcher: U, description: String? = nil) { + let (pass, msg) = expressionDoesNotMatch(expression, matcher: matcher, toNot: "to not", description: description) verify(pass, msg) } /// Tests the actual value using a matcher to not match. /// /// Alias to toNot(). - public func notTo(matcher: U) { - toNot(matcher) + public func notTo(matcher: U, description: String? = nil) { + toNot(matcher, description: description) } // see: diff --git a/Nimble/FailureMessage.swift b/Nimble/FailureMessage.swift index 1d5392e85..bacb71b35 100644 --- a/Nimble/FailureMessage.swift +++ b/Nimble/FailureMessage.swift @@ -9,6 +9,7 @@ public class FailureMessage: NSObject { public var to: String = "to" public var postfixMessage: String = "match" public var postfixActual: String = "" + public var userDescription: String? = nil public var stringValue: String { get { @@ -44,6 +45,12 @@ public class FailureMessage: NSObject { if let actualValue = actualValue { value = "\(expected) \(to) \(postfixMessage), got \(actualValue)\(postfixActual)" } - return stripNewlines(value) + value = stripNewlines(value) + + if let userDescription = userDescription { + return "\(userDescription)\n\(value)" + } + + return value } } \ No newline at end of file diff --git a/Nimble/ObjCExpectation.swift b/Nimble/ObjCExpectation.swift index 873bdece1..643a91a8e 100644 --- a/Nimble/ObjCExpectation.swift +++ b/Nimble/ObjCExpectation.swift @@ -63,7 +63,8 @@ public class NMBExpectation : NSObject { return ({ matcher in self.expectValue.toEventually( ObjCMatcherWrapper(matcher: matcher), - timeout: self._timeout + timeout: self._timeout, + description: nil ) }) } @@ -72,7 +73,8 @@ public class NMBExpectation : NSObject { return ({ matcher in self.expectValue.toEventuallyNot( ObjCMatcherWrapper(matcher: matcher), - timeout: self._timeout + timeout: self._timeout, + description: nil ) }) } diff --git a/Nimble/Wrappers/AsyncMatcherWrapper.swift b/Nimble/Wrappers/AsyncMatcherWrapper.swift index 8d30b7c61..3e373de06 100644 --- a/Nimble/Wrappers/AsyncMatcherWrapper.swift +++ b/Nimble/Wrappers/AsyncMatcherWrapper.swift @@ -46,7 +46,7 @@ private let toEventuallyRequiresClosureError = FailureMessage(stringValue: "expe extension Expectation { /// Tests the actual value using a matcher to match by checking continuously /// at each pollInterval until the timeout is reached. - public func toEventually(matcher: U, timeout: NSTimeInterval = 1, pollInterval: NSTimeInterval = 0.01) { + public func toEventually(matcher: U, timeout: NSTimeInterval = 1, pollInterval: NSTimeInterval = 0.01, description: String? = nil) { if expression.isClosure { let (pass, msg) = expressionMatches( expression, @@ -54,7 +54,8 @@ extension Expectation { fullMatcher: matcher, timeoutInterval: timeout, pollInterval: pollInterval), - to: "to eventually" + to: "to eventually", + description: description ) verify(pass, msg) } else { @@ -64,7 +65,7 @@ extension Expectation { /// Tests the actual value using a matcher to not match by checking /// continuously at each pollInterval until the timeout is reached. - public func toEventuallyNot(matcher: U, timeout: NSTimeInterval = 1, pollInterval: NSTimeInterval = 0.01) { + public func toEventuallyNot(matcher: U, timeout: NSTimeInterval = 1, pollInterval: NSTimeInterval = 0.01, description: String? = nil) { if expression.isClosure { let (pass, msg) = expressionDoesNotMatch( expression, @@ -72,7 +73,8 @@ extension Expectation { fullMatcher: matcher, timeoutInterval: timeout, pollInterval: pollInterval), - toNot: "to eventually not" + toNot: "to eventually not", + description: description ) verify(pass, msg) } else { @@ -84,7 +86,7 @@ extension Expectation { /// continuously at each pollInterval until the timeout is reached. /// /// Alias of toEventuallyNot() - public func toNotEventually(matcher: U, timeout: NSTimeInterval = 1, pollInterval: NSTimeInterval = 0.01) { - return toEventuallyNot(matcher, timeout: timeout, pollInterval: pollInterval) + public func toNotEventually(matcher: U, timeout: NSTimeInterval = 1, pollInterval: NSTimeInterval = 0.01, description: String? = nil) { + return toEventuallyNot(matcher, timeout: timeout, pollInterval: pollInterval, description: description) } } diff --git a/NimbleTests/AsynchronousTest.swift b/NimbleTests/AsynchronousTest.swift index fa209c3f9..961d73349 100644 --- a/NimbleTests/AsynchronousTest.swift +++ b/NimbleTests/AsynchronousTest.swift @@ -57,4 +57,28 @@ class AsyncTest: XCTestCase { } } } + + func testToEventuallyMatch_CustomFailureMessage() { + failsWithErrorMessage( + "These aren't eventually equal!\n" + + "expected to eventually equal <1>, got <0>") { + expect { 0 }.toEventually(equal(1), description: "These aren't eventually equal!") + } + } + + func testToEventuallyNotMatch_CustomFailureMessage() { + failsWithErrorMessage( + "These are eventually equal!\n" + + "expected to eventually not equal <1>, got <1>") { + expect { 1 }.toEventuallyNot(equal(1), description: "These are eventually equal!") + } + } + + func testToNotEventuallyMatch_CustomFailureMessage() { + failsWithErrorMessage( + "These are eventually equal!\n" + + "expected to eventually not equal <1>, got <1>") { + expect { 1 }.toEventuallyNot(equal(1), description: "These are eventually equal!") + } + } } diff --git a/NimbleTests/SynchronousTests.swift b/NimbleTests/SynchronousTests.swift index 20e1f26c2..1e2ef7d4e 100644 --- a/NimbleTests/SynchronousTests.swift +++ b/NimbleTests/SynchronousTests.swift @@ -87,8 +87,31 @@ class SynchronousTest: XCTestCase { } } - func testNotToMatchesLikeToNot() { expect(1).notTo(MatcherFunc { expr, failure in false }) } + + func testToMatcher_CustomFailureMessage() { + failsWithErrorMessage( + "These aren't equal!\n" + + "expected to match, got <1>") { + expect(1).to(MatcherFunc { expr, failure in false }, description: "These aren't equal!") + } + } + + func testNotToMatcher_CustomFailureMessage() { + failsWithErrorMessage( + "These aren't equal!\n" + + "expected to not match, got <1>") { + expect(1).notTo(MatcherFunc { expr, failure in true }, description: "These aren't equal!") + } + } + + func testToNotMatcher_CustomFailureMessage() { + failsWithErrorMessage( + "These aren't equal!\n" + + "expected to not match, got <1>") { + expect(1).toNot(MatcherFunc { expr, failure in true }, description: "These aren't equal!") + } + } } diff --git a/README.md b/README.md index 1c0195edd..45151ae64 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,21 @@ expect(seagull.squawk).toNot(equal(@"Oh, hello there!")); expect(seagull.squawk).notTo(equal(@"Oh, hello there!")); ``` +## Custom Failure Messages + +Would you like to add more information to the test's failure messages? Use the `description` optional argument to add your own text: + +```swift +// Swift + +expect(1 + 1).to(equal(3)) +// failed - expected to equal <3>, got <2> + +expect(1 + 1).to(equal(3), description: "Make sure libKindergartenMath is loaded") +// failed - Make sure libKindergartenMath is loaded +// expected to equal <3>, got <2> +``` + ## Type Checking Nimble makes sure you don't compare two types that don't match: @@ -1048,4 +1063,4 @@ target 'YOUR_APP_NAME_HERE_Tests', :exclusive => true do end ``` -Finally run `pod install`. +Finally run `pod install`.