A Matcher Framework for Swift and Objective-C.
Table of Contents generated with DocToc
Usage
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))
Asynchronous Expectations
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 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() }
List of Builtin Matchers
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 istrue(forBooleanTypesupported types likebool).beFalsy(): Matches if the given value isfalse(forBooleanTypesupported types likebool).contain(items: T...)Matches if all ofitemsare in the given container. Valid containers are Swift collections that haveEquatableelements;NSArraysandNSSets; andStrings- which use substring matching.beginWith(starting: T)Matches ifstartingis in beginning the given container. Valid containers are Swift collections that haveEquatableelements;NSArrays; andStrings- which use substring matching.endWith(ending: T)Matches ifendingis at the end of the given container. Valid containers are Swift collections that haveEquatableelements;NSArrays; andStrings- which use substring matching.beIdenticalTo(expectedInstance: T)(also===and!==operators) Matches ifexpectedInstancehas 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 theexpectedClassusingisMemberOfClass:. Only works with Objective-C compatible objects.beAKindOf(expectedClass: Class)Matches if the given object is theexpectedClassusingisKindOfClass:. 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
Using Nimble in Objective-C
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());
Writing Your Own Matchers
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 // ... } }
Customizing Failure Messages
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
Supporting Objective-C
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
Installing Nimble
Currently, you must add this project as a subproject and link against the Nimble.framework.
See How to Install Quick which walks through how to set up Quick and Nimble. Simply ignoring the Quick setup and just follow the Nimble setup.