A Matcher Framework for Swift. Rewritten from scratch.
Currently, this is not part of the Quick project.
Currently, you must add this project as a subproject and link against the Nimble.framework.
Matchers follow Cedar's design. They're generic-based:
import Nimble
// ...
expect(1).to(equal(1))
expect(1.2).to(beCloseTo(1.1, within: 1))
Certain operators work as expected too:
expect("foo") != "bar"
expect(10) > 2
The expect
function autocompletes to include file:
and line:
, but these are optional.
The defaults will populate the current file and line.
Also, expect
takes a lazily computed value. This makes it possible
to handle exceptions in-line (even though Swift doesn't support exceptions):
var exception = NSException(name: "laugh", reason: "Lulz", userInfo: nil)
expect(exception.raise()).to(raiseException(named: "laugh"))
Or you can use trailing-closure style as needed:
expect {
"hello"
}.to(equalTo("hello"))
C primitives are allowed without any wrapping:
let actual: CInt = 1
let expectedValue: CInt = 1
expect(actual).to(equal(expectedValue))
In fact, type inference is used to remove redudant type specifying:
// both work
expect(1 as CInt).to(equal(1))
expect(1).to(equal(1 as CInt))
Simply exchange to
and toNot
with toEventually
and toEventuallyNot
:
var value = 0
dispatch_async(dispatch_get_main_queue()) {
value = 1
}
expect(value).toEventually(equal(1))
This polls the expression inside expect(...)
until the given expectation succeeds
within a 1 second. You can explicitly pass the timeout
parameter:
expect(value).toEventually(equal(1), timeout: 1)
If you prefer the callback-style that some testing frameworks do, use waitUntil
:
waitUntil { done in
// do some stuff that takes a while...
NSThread.sleepForTimeInterval(0.5)
done()
}
And like the other asynchronous expectation, an optional timeout period can be provided:
waitUntil(timeout: 10) { done in
// do some stuff that takes a while...
NSThread.sleepForTimeInterval(1)
done()
}
The following matchers are currently included with Nimble:
equal(expectedValue)
(also==
and!=
operators). Values must beEquatable
,Comparable
, orNSObjects
.beCloseTo(expectedValue, within: Double = 0.0001)
. Values must be coercable into aDouble
.beLessThan(upperLimit)
(also<
operator). Values must beComparable
.beLessThanOrEqualTo(upperLimit)
(also<=
operator). Values must beComparable
.beGreaterThan(lowerLimit)
(also>
operator). Values must beComparable
.beGreaterThanOrEqualTo(lowerLimit)
(also>=
operator). Values must beComparable
,raiseException()
Matches if the given closure raises an exception.raiseException(#named: String?)
Matches if the given closure raises an exception with the given name.raiseException(#named: String?, #reason: String?)
Matches if the given closure raises an exception with the given name and reason.beNil()
Matches if the given value isnil
.beTruthy()
: Matches if the given value is notnil
(for optionals) orfalse
(forLogicValue
supported types likebool
). Optional Bools are automatically unwrapped first.beFalsy()
: Matches if the given value isnil
(for optionals) orfalse
(forLogicValue
supported types likebool
). Optional Bools are automatically unwrapped first.contain(items: T...)
Matches if all ofitems
are in the given container. Valid containers are Swift collections that haveEquatable
elements;NSArrays
andNSSets
; andStrings
- which use substring matching.beginWith(starting: T)
Matches ifstarting
is in beginning the given container. Valid containers are Swift collections that haveEquatable
elements;NSArrays
; andStrings
- which use substring matching.endWith(ending: T)
Matches ifending
is at the end of the given container. Valid containers are Swift collections that haveEquatable
elements;NSArrays
; andStrings
- which use substring matching.beIdenticalTo(expectedInstance: T)
(also===
and!==
operators) Matches ifexpectedInstance
has the same pointer address (identity equality) with the given value. Only works with Objective-C compatible objects.beAnInstanceOf(expectedClass: Class)
Matches if the given object is theexpectedClass
usingisMemberOfClass:
. Only works with Objective-C compatible objects.beASubclassOf(expectedClass: Class)
Matches if the given object is theexpectedClass
usingisKindOfClass:
. Only works with Objective-C compatible objects.beEmpty()
Matches if the given type contains nothing. Works with Strings and Collections from both Swift and Objective-C
Experimental Support
Want to use this for Objective-C? The same syntax applies except you must use Objective-C objects:
#import <Nimble/Nimble.h>
// ...
expect(@1).to(equal(@1));
expect(@1.2).to(beCloseTo(@1.3).within(@0.5));
expect(@[@1, @2]).to(contain(@1));
For exceptions, use expectAction
, which ignores the expression returned:
expectAction([exception raise]).to(raiseException());
Most matchers can be defined using MatcherFunc
:
func equal<T: Equatable>(expectedValue: T?) -> MatcherFunc<T> {
return MatcherFunc { actualExpression, failureMessage in
failureMessage.postfixMessage = "equal <\(expectedValue)>"
return actualExpression.evaluate() == expectedValue
}
}
The return value inside MatcherFunc
closure is a Bool
that indicates success
or failure to match.
actualExpression
is a lazy, memoized closure around the value provided to
expect(...)
.
Using Swift's generics, matchers can constrain the type of the actual value received
from expect(<actualValue>)
by modifying the return type:
@objc protocol FuzzyThing { } // objc for objc support (see Objective-C section below)
// Only expect(fuzzyObject).to(beFuzzy()) is allowed by the compiler,
// where fuzzyObject supports the FuzzyThing protocol or is nil.
func beFuzzy() -> MatcherFunc<FuzzyThing?> {
return MatcherFunc { actualExpression, failureMessage in
// ...
}
}
failureMessage
is a structure of the final expectation message to emit. If you
want to suppress emitting the actual value, you can nil out actualValue
in your
matcher:
failureMessage.actualValue = nil
failureMessage.postfixMessage = "yo"
// resulting error: expected to yo
Since Swift generics cannot interop with Objective-C, you need to wrap your matchers
and expose them as regular C-functions. The common location is to place them in
NMBObjCMatcher
:
// Swift
extension NMBObjCMatcher {
class func beFuzzyMatcher() -> NMBObjCMatcher {
return NMBObjCMatcher { actualBlock, failureMessage, location in
let expr = Expression(expression: ({ actualBlock() as FuzzyThing? }), location: location)
return beFuzzy().matches(expr, failureMessage: failureMessage)
}
}
}
Afterwards, you'll probably want a nice interface for usage:
// Objective-C
FOUNDATION_EXPORT id<NMBMatcher> beFuzzy() {
return [NMBObjCMatcher beFuzzyMatcher];
}
When supporting Objective-C, make sure you handle nil
appropriately. Like Cedar,
most matchers do not match with nil. This is to prevent accidental nil-fallthroughs:
expect(nil).to(equal(nil)); // fails
Which beNil()
allows for explicit resolution:
expect(nil).to(beNil()); // passes