Name: Nimble
Owner: Quick
Description: A Matcher Framework for Swift and Objective-C
Created: 2014-06-28 11:28:38.0
Updated: 2018-01-18 21:40:21.0
Pushed: 2017-12-28 23:21:13.0
Size: 1749
Language: Swift
GitHub Committers
User | Most Recent Commit | # Commits |
---|
Other Committers
User | Most Recent Commit | # Commits |
---|
Use Nimble to express the expected outcomes of Swift or Objective-C expressions. Inspired by Cedar.
wift
ct(1 + 1).to(equal(2))
ct(1.2).to(beCloseTo(1.1, within: 0.1))
ct(3) > 2
ct("seahorse").to(contain("sea"))
ct(["Atlantic", "Pacific"]).toNot(contain("Mississippi"))
ct(ocean.isClean).toEventually(beTruthy())
Table of Contents generated with DocToc
expect(...).to
Apple's Xcode includes the XCTest framework, which provides
assertion macros to test whether code behaves properly.
For example, to assert that 1 + 1 = 2
, XCTest has you write:
wift
ssertEqual(1 + 1, 2, "expected one plus one to equal two")
Or, in Objective-C:
bjective-C
ssertEqual(1 + 1, 2, @"expected one plus one to equal two");
XCTest assertions have a couple of drawbacks:
Nimble addresses these concerns.
expect(...).to
Nimble allows you to express expectations using a natural, easily understood language:
wift
rt Nimble
ct(seagull.squawk).to(equal("Squee!"))
bjc
bjective-C
ort Nimble;
ct(seagull.squawk).to(equal(@"Squee!"));
The
expect
function autocompletes to includefile:
andline:
, but these parameters are optional. Use the default values to have Xcode highlight the correct line when an expectation is not met.
To perform the opposite expectation–to assert something is not
equal–use toNot
or notTo
:
wift
rt Nimble
ct(seagull.squawk).toNot(equal("Oh, hello there!"))
ct(seagull.squawk).notTo(equal("Oh, hello there!"))
bjc
bjective-C
ort Nimble;
ct(seagull.squawk).toNot(equal(@"Oh, hello there!"));
ct(seagull.squawk).notTo(equal(@"Oh, hello there!"));
Would you like to add more information to the test's failure messages? Use the description
optional argument to add your own text:
wift
ct(1 + 1).to(equal(3))
ailed - expected to equal <3>, got <2>
ct(1 + 1).to(equal(3), description: "Make sure libKindergartenMath is loaded")
ailed - Make sure libKindergartenMath is loaded
xpected to equal <3>, got <2>
Or the *WithDescription version in Objective-C:
bjective-C
ort Nimble;
ct(@(1+1)).to(equal(@3));
ailed - expected to equal <3.0000>, got <2.0000>
ct(@(1+1)).toWithDescription(equal(@3), @"Make sure libKindergartenMath is loaded");
ailed - Make sure libKindergartenMath is loaded
xpected to equal <3.0000>, got <2.0000>
Nimble makes sure you don't compare two types that don't match:
wift
oes not compile:
ct(1 + 1).to(equal("Squee!"))
Nimble uses generics–only available in Swift–to ensure type correctness. That means type checking is not available when using Nimble in Objective-C. :sob:
Tired of so much typing? With Nimble, you can use overloaded operators
like ==
for equivalence, or >
for comparisons:
wift
asses if squawk does not equal "Hi!":
ct(seagull.squawk) != "Hi!"
asses if 10 is greater than 2:
ct(10) > 2
Operator overloads are only available in Swift, so you won't be able to use this syntax in Objective-C. :broken_heart:
The expect
function doesn't evaluate the value it's given until it's
time to match. So Nimble can test whether an expression raises an
exception once evaluated:
wift
ote: Swift currently doesn't have exceptions.
Only Objective-C code can raise exceptions
that Nimble will catch.
(see https://github.com/Quick/Nimble/issues/220#issuecomment-172667064)
exception = NSException(
me: NSInternalInconsistencyException,
ason: "Not enough fish in the sea.",
erInfo: ["something": "is fishy"])
ct { exception.raise() }.to(raiseException())
lso, you can customize raiseException to be more specific
ct { exception.raise() }.to(raiseException(named: NSInternalInconsistencyException))
ct { exception.raise() }.to(raiseException(
named: NSInternalInconsistencyException,
reason: "Not enough fish in the sea"))
ct { exception.raise() }.to(raiseException(
named: NSInternalInconsistencyException,
reason: "Not enough fish in the sea",
userInfo: ["something": "is fishy"]))
Objective-C works the same way, but you must use the expectAction
macro when making an expectation on an expression that has no return
value:
bjective-C
ception *exception = [NSException exceptionWithName:NSInternalInconsistencyException
reason:@"Not enough fish in the sea."
userInfo:nil];
ctAction(^{ [exception raise]; }).to(raiseException());
se the property-block syntax to be more specific.
ctAction(^{ [exception raise]; }).to(raiseException().named(NSInternalInconsistencyException));
ctAction(^{ [exception raise]; }).to(raiseException().
named(NSInternalInconsistencyException).
reason("Not enough fish in the sea"));
ctAction(^{ [exception raise]; }).to(raiseException().
named(NSInternalInconsistencyException).
reason("Not enough fish in the sea").
userInfo(@{@"something": @"is fishy"}));
ou can also pass a block for custom matching of the raised exception
ctAction(exception.raise()).to(raiseException().satisfyingBlock(^(NSException *exception) {
expect(exception.name).to(beginWith(NSInternalInconsistencyException));
Some testing frameworks make it hard to test primitive C values. In Nimble, it just works:
wift
actual: CInt = 1
expectedValue: CInt = 1
ct(actual).to(equal(expectedValue))
In fact, Nimble uses type inference, so you can write the above without explicitly specifying both types:
wift
ct(1 as CInt).to(equal(1))
In Objective-C, Nimble only supports Objective-C objects. To make expectations on primitive C values, wrap then in an object literal:
ct(@(1 + 1)).to(equal(@2));
In Nimble, it's easy to make expectations on values that are updated
asynchronously. Just use toEventually
or toEventuallyNot
:
wift 3.0 and later
atchQueue.main.async {
ocean.add("dolphins")
ocean.add("whales")
ct(ocean).toEventually(contain("dolphins", "whales"))
wift
wift 2.3 and earlier
atch_async(dispatch_get_main_queue()) {
ocean.add("dolphins")
ocean.add("whales")
ct(ocean).toEventually(contain("dolphins", "whales"))
bjc
bjective-C
atch_async(dispatch_get_main_queue(), ^{
[ocean add:@"dolphins"];
[ocean add:@"whales"];
ct(ocean).toEventually(contain(@"dolphins", @"whales"));
Note: toEventually triggers its polls on the main thread. Blocking the main thread will cause Nimble to stop the run loop. This can cause test pollution for whatever incomplete code that was running on the main thread. Blocking the main thread can be caused by blocking IO, calls to sleep(), deadlocks, and synchronous IPC.
In the above example, ocean
is constantly re-evaluated. If it ever
contains dolphins and whales, the expectation passes. If ocean
still
doesn't contain them, even after being continuously re-evaluated for one
whole second, the expectation fails.
Sometimes it takes more than a second for a value to update. In those
cases, use the timeout
parameter:
wift
aits three seconds for ocean to contain "starfish":
ct(ocean).toEventually(contain("starfish"), timeout: 3)
valuate someValue every 0.2 seconds repeatedly until it equals 100, or fails if it timeouts after 5.5 seconds.
ct(someValue).toEventually(equal(100), timeout: 5.5, pollInterval: 0.2)
bjc
bjective-C
aits three seconds for ocean to contain "starfish":
ct(ocean).withTimeout(3).toEventually(contain(@"starfish"));
You can also provide a callback by using the waitUntil
function:
wift
Until { done in
ocean.goFish { success in
expect(success).to(beTrue())
done()
}
bjc
bjective-C
Until(^(void (^done)(void)){
[ocean goFishWithHandler:^(BOOL success){
expect(success).to(beTrue());
done();
}];
waitUntil
also optionally takes a timeout parameter:
wift
Until(timeout: 10) { done in
ocean.goFish { success in
expect(success).to(beTrue())
done()
}
bjc
bjective-C
UntilTimeout(10, ^(void (^done)(void)){
[ocean goFishWithHandler:^(BOOL success){
expect(success).to(beTrue());
done();
}];
Note: waitUntil
triggers its timeout code on the main thread. Blocking the main
thread will cause Nimble to stop the run loop to continue. This can cause test
pollution for whatever incomplete code that was running on the main thread.
Blocking the main thread can be caused by blocking IO, calls to sleep(),
deadlocks, and synchronous IPC.
In some cases (e.g. when running on slower machines) it can be useful to modify the default timeout and poll interval values. This can be done as follows:
wift
ncrease the global timeout to 5 seconds:
le.AsyncDefaults.Timeout = 5
low the polling interval to 0.1 seconds:
le.AsyncDefaults.PollInterval = 0.1
Nimble has full support for Objective-C. However, there are two things to keep in mind when using Nimble in Objective-C:
All parameters passed to the expect
function, as well as matcher
functions like equal
, must be Objective-C objects or can be converted into
an NSObject
equivalent:
bjective-C
ort Nimble;
ct(@(1 + 1)).to(equal(@2));
ct(@"Hello world").to(contain(@"world"));
oxed as NSNumber *
ct(2).to(equal(2));
ct(1.2).to(beLessThan(2.0));
ct(true).to(beTruthy());
oxed as NSString *
ct("Hello world").to(equal("Hello world"));
oxed as NSRange
ct(NSMakeRange(1, 10)).to(equal(NSMakeRange(1, 10)));
To make an expectation on an expression that does not return a value,
such as -[NSException raise]
, use expectAction
instead of
expect
:
bjective-C
ctAction(^{ [exception raise]; }).to(raiseException());
The following types are currently converted to an NSObject
type:
NSNumber *
NSRange
is converted to NSValue *
char *
is converted to NSString *
For the following matchers:
equal
beGreaterThan
beGreaterThanOrEqual
beLessThan
beLessThanOrEqual
beCloseTo
beTrue
beFalse
beTruthy
beFalsy
haveCount
If you would like to see more, file an issue.
Nimble provides a shorthand for expressing expectations using the
expect
function. To disable this shorthand in Objective-C, define the
NIMBLE_DISABLE_SHORT_SYNTAX
macro somewhere in your code before
importing Nimble:
ine NIMBLE_DISABLE_SHORT_SYNTAX 1
ort Nimble;
expect(^{ return seagull.squawk; }, __FILE__, __LINE__).to(NMB_equal(@"Squee!"));
Disabling the shorthand is useful if you're testing functions with names that conflict with Nimble functions, such as
expect
orequal
. If that's not the case, there's no point in disabling the shorthand.
Nimble includes a wide variety of matcher functions.
Nimble supports checking the type membership of any kind of object, whether Objective-C conformant or not:
wift
ocol SomeProtocol{}
s SomeClassConformingToProtocol: SomeProtocol{}
ct SomeStructConformingToProtocol: SomeProtocol{}
he following tests pass
ct(1).to(beAKindOf(Int.self))
ct("turtle").to(beAKindOf(String.self))
classObject = SomeClassConformingToProtocol()
ct(classObject).to(beAKindOf(SomeProtocol.self))
ct(classObject).to(beAKindOf(SomeClassConformingToProtocol.self))
ct(classObject).toNot(beAKindOf(SomeStructConformingToProtocol.self))
structObject = SomeStructConformingToProtocol()
ct(structObject).to(beAKindOf(SomeProtocol.self))
ct(structObject).to(beAKindOf(SomeStructConformingToProtocol.self))
ct(structObject).toNot(beAKindOf(SomeClassConformingToProtocol.self))
bjc
bjective-C
he following tests pass
tableArray *array = [NSMutableArray array];
ct(array).to(beAKindOf([NSArray class]));
ct(@1).toNot(beAKindOf([NSNull class]));
Objects can be tested for their exact types using the beAnInstanceOf
matcher:
wift
ocol SomeProtocol{}
s SomeClassConformingToProtocol: SomeProtocol{}
ct SomeStructConformingToProtocol: SomeProtocol{}
nlike the 'beKindOf' matcher, the 'beAnInstanceOf' matcher only
asses if the object is the EXACT type requested. The following
ests pass -- note its behavior when working in an inheritance hierarchy.
ct(1).to(beAnInstanceOf(Int.self))
ct("turtle").to(beAnInstanceOf(String.self))
classObject = SomeClassConformingToProtocol()
ct(classObject).toNot(beAnInstanceOf(SomeProtocol.self))
ct(classObject).to(beAnInstanceOf(SomeClassConformingToProtocol.self))
ct(classObject).toNot(beAnInstanceOf(SomeStructConformingToProtocol.self))
structObject = SomeStructConformingToProtocol()
ct(structObject).toNot(beAnInstanceOf(SomeProtocol.self))
ct(structObject).to(beAnInstanceOf(SomeStructConformingToProtocol.self))
ct(structObject).toNot(beAnInstanceOf(SomeClassConformingToProtocol.self))
wift
asses if 'actual' is equivalent to 'expected':
ct(actual).to(equal(expected))
ct(actual) == expected
asses if 'actual' is not equivalent to 'expected':
ct(actual).toNot(equal(expected))
ct(actual) != expected
bjc
bjective-C
asses if 'actual' is equivalent to 'expected':
ct(actual).to(equal(expected))
asses if 'actual' is not equivalent to 'expected':
ct(actual).toNot(equal(expected))
Values must be Equatable
, Comparable
, or subclasses of NSObject
.
equal
will always fail when used to compare one or more nil
values.
wift
asses if 'actual' has the same pointer address as 'expected':
ct(actual).to(beIdenticalTo(expected))
ct(actual) === expected
asses if 'actual' does not have the same pointer address as 'expected':
ct(actual).toNot(beIdenticalTo(expected))
ct(actual) !== expected
It is important to remember that beIdenticalTo
only makes sense when comparing
types with reference semantics, which have a notion of identity. In Swift,
that means types that are defined as a class
.
This matcher will not work when comparing types with value semantics such as
those defined as a struct
or enum
. If you need to compare two value types,
consider what it means for instances of your type to be identical. This may mean
comparing individual properties or, if it makes sense to do so, conforming your type
to Equatable
and using Nimble's equivalence matchers instead.
bjective-C
asses if 'actual' has the same pointer address as 'expected':
ct(actual).to(beIdenticalTo(expected));
asses if 'actual' does not have the same pointer address as 'expected':
ct(actual).toNot(beIdenticalTo(expected));
wift
ct(actual).to(beLessThan(expected))
ct(actual) < expected
ct(actual).to(beLessThanOrEqualTo(expected))
ct(actual) <= expected
ct(actual).to(beGreaterThan(expected))
ct(actual) > expected
ct(actual).to(beGreaterThanOrEqualTo(expected))
ct(actual) >= expected
bjc
bjective-C
ct(actual).to(beLessThan(expected));
ct(actual).to(beLessThanOrEqualTo(expected));
ct(actual).to(beGreaterThan(expected));
ct(actual).to(beGreaterThanOrEqualTo(expected));
Values given to the comparison matchers above must implement
Comparable
.
Because of how computers represent floating point numbers, assertions
that two floating point numbers be equal will sometimes fail. To express
that two numbers should be close to one another within a certain margin
of error, use beCloseTo
:
wift
ct(actual).to(beCloseTo(expected, within: delta))
bjc
bjective-C
ct(actual).to(beCloseTo(expected).within(delta));
For example, to assert that 10.01
is close to 10
, you can write:
wift
ct(10.01).to(beCloseTo(10, within: 0.1))
bjc
bjective-C
ct(@(10.01)).to(beCloseTo(@10).within(0.1));
There is also an operator shortcut available in Swift:
wift
ct(actual) ? expected
ct(actual) ? (expected, delta)
(Type Option-x to get ? on a U.S. keyboard)
The former version uses the default delta of 0.0001. Here is yet another way to do this:
wift
ct(actual) ? expected ± delta
ct(actual) == expected ± delta
(Type Option-Shift-= to get ± on a U.S. keyboard)
If you are comparing arrays of floating point numbers, you'll find the following useful:
wift
ct([0.0, 2.0]) ? [0.0001, 2.0001]
ct([0.0, 2.0]).to(beCloseTo([0.1, 2.1], within: 0.1))
Values given to the
beCloseTo
matcher must be coercable into aDouble
.
wift
asses if 'instance' is an instance of 'aClass':
ct(instance).to(beAnInstanceOf(aClass))
asses if 'instance' is an instance of 'aClass' or any of its subclasses:
ct(instance).to(beAKindOf(aClass))
bjc
bjective-C
asses if 'instance' is an instance of 'aClass':
ct(instance).to(beAnInstanceOf(aClass));
asses if 'instance' is an instance of 'aClass' or any of its subclasses:
ct(instance).to(beAKindOf(aClass));
Instances must be Objective-C objects: subclasses of
NSObject
, or Swift objects bridged to Objective-C with the@objc
prefix.
For example, to assert that dolphin
is a kind of Mammal
:
wift
ct(dolphin).to(beAKindOf(Mammal))
bjc
bjective-C
ct(dolphin).to(beAKindOf([Mammal class]));
beAnInstanceOf
uses the-[NSObject isMemberOfClass:]
method to test membership.beAKindOf
uses-[NSObject isKindOfClass:]
.
asses if 'actual' is not nil, true, or an object with a boolean value of true:
ct(actual).to(beTruthy())
asses if 'actual' is only true (not nil or an object conforming to Boolean true):
ct(actual).to(beTrue())
asses if 'actual' is nil, false, or an object with a boolean value of false:
ct(actual).to(beFalsy())
asses if 'actual' is only false (not nil or an object conforming to Boolean false):
ct(actual).to(beFalse())
asses if 'actual' is nil:
ct(actual).to(beNil())
bjc
bjective-C
asses if 'actual' is not nil, true, or an object with a boolean value of true:
ct(actual).to(beTruthy());
asses if 'actual' is only true (not nil or an object conforming to Boolean true):
ct(actual).to(beTrue());
asses if 'actual' is nil, false, or an object with a boolean value of false:
ct(actual).to(beFalsy());
asses if 'actual' is only false (not nil or an object conforming to Boolean false):
ct(actual).to(beFalse());
asses if 'actual' is nil:
ct(actual).to(beNil());
If you're using Swift, you can use the throwAssertion
matcher to check if an assertion is thrown (e.g. fatalError()
). This is made possible by @mattgallagher's CwlPreconditionTesting library.
wift
asses if 'somethingThatThrows()' throws an assertion,
uch as by calling 'fatalError()' or if a precondition fails:
ct { try somethingThatThrows() }.to(throwAssertion())
ct { () -> Void in fatalError() }.to(throwAssertion())
ct { precondition(false) }.to(throwAssertion())
asses if throwing an NSError is not equal to throwing an assertion:
ct { throw NSError(domain: "test", code: 0, userInfo: nil) }.toNot(throwAssertion())
asses if the code after the precondition check is not run:
reachedPoint1 = false
reachedPoint2 = false
ct {
reachedPoint1 = true
precondition(false, "condition message")
reachedPoint2 = true
(throwAssertion())
ct(reachedPoint1) == true
ct(reachedPoint2) == false
Notes:
x86_64
binaries, meaning you cannot run this matcher on iOS devices, only simulators.Debug executable
scheme setting for your tvOS scheme's Test configuration.If you're using Swift 2.0 or newer, you can use the throwError
matcher to check if an error is thrown.
Note:
The following code sample references the Swift.Error
protocol.
This is Swift.ErrorProtocol
in versions of Swift prior to version 3.0.
wift
asses if 'somethingThatThrows()' throws an 'Error':
ct { try somethingThatThrows() }.to(throwError())
asses if 'somethingThatThrows()' throws an error within a particular domain:
ct { try somethingThatThrows() }.to(throwError { (error: Error) in
expect(error._domain).to(equal(NSCocoaErrorDomain))
asses if 'somethingThatThrows()' throws a particular error enum case:
ct { try somethingThatThrows() }.to(throwError(NSCocoaError.PropertyListReadCorruptError))
asses if 'somethingThatThrows()' throws an error of a particular type:
ct { try somethingThatThrows() }.to(throwError(errorType: NimbleError.self))
When working directly with Error
values, using the matchError
matcher
allows you to perform certain checks on the error itself without having to
explicitly cast the error.
The matchError
matcher allows you to check whether or not the error:
This can be useful when using Result
or Promise
types, for example.
wift
actual: Error = ...
asses if 'actual' represents any error value from the NimbleErrorEnum type:
ct(actual).to(matchError(NimbleErrorEnum.self))
asses if 'actual' represents the case 'timeout' from the NimbleErrorEnum type:
ct(actual).to(matchError(NimbleErrorEnum.timeout))
asses if 'actual' contains an NSError equal to the one provided:
ct(actual).to(matchError(NSError(domain: "err", code: 123, userInfo: nil)))
Note: This feature is only available in Swift.
wift
asses if 'actual', when evaluated, raises an exception:
ct(actual).to(raiseException())
asses if 'actual' raises an exception with the given name:
ct(actual).to(raiseException(named: name))
asses if 'actual' raises an exception with the given name and reason:
ct(actual).to(raiseException(named: name, reason: reason))
asses if 'actual' raises an exception which passes expectations defined in the given closure:
in this case, if the exception's name begins with "a r")
ct { exception.raise() }.to(raiseException { (exception: NSException) in
expect(exception.name).to(beginWith("a r"))
bjc
bjective-C
asses if 'actual', when evaluated, raises an exception:
ct(actual).to(raiseException())
asses if 'actual' raises an exception with the given name
ct(actual).to(raiseException().named(name))
asses if 'actual' raises an exception with the given name and reason:
ct(actual).to(raiseException().named(name).reason(reason))
asses if 'actual' raises an exception and it passes expectations defined in the given block:
in this case, if name begins with "a r")
ct(actual).to(raiseException().satisfyingBlock(^(NSException *exception) {
expect(exception.name).to(beginWith(@"a r"));
Note: Swift currently doesn't have exceptions (see #220). Only Objective-C code can raise exceptions that Nimble will catch.
wift
asses if all of the expected values are members of 'actual':
ct(actual).to(contain(expected...))
asses if 'actual' is empty (i.e. it contains no elements):
ct(actual).to(beEmpty())
bjc
bjective-C
asses if expected is a member of 'actual':
ct(actual).to(contain(expected));
asses if 'actual' is empty (i.e. it contains no elements):
ct(actual).to(beEmpty());
In Swift
contain
takes any number of arguments. The expectation passes if all of them are members of the collection. In Objective-C,contain
only takes one argument for now.
For example, to assert that a list of sea creature names contains “dolphin” and “starfish”:
wift
ct(["whale", "dolphin", "starfish"]).to(contain("dolphin", "starfish"))
bjc
bjective-C
ct(@[@"whale", @"dolphin", @"starfish"]).to(contain(@"dolphin"));
ct(@[@"whale", @"dolphin", @"starfish"]).to(contain(@"starfish"));
contain
andbeEmpty
expect collections to be instances ofNSArray
,NSSet
, or a Swift collection composed ofEquatable
elements.
To test whether a set of elements is present at the beginning or end of
an ordered collection, use beginWith
and endWith
:
wift
asses if the elements in expected appear at the beginning of 'actual':
ct(actual).to(beginWith(expected...))
asses if the the elements in expected come at the end of 'actual':
ct(actual).to(endWith(expected...))
bjc
bjective-C
asses if the elements in expected appear at the beginning of 'actual':
ct(actual).to(beginWith(expected));
asses if the the elements in expected come at the end of 'actual':
ct(actual).to(endWith(expected));
beginWith
andendWith
expect collections to be instances ofNSArray
, or ordered Swift collections composed ofEquatable
elements.Like
contain
, in Objective-CbeginWith
andendWith
only support a single argument for now.
For code that returns collections of complex objects without a strict
ordering, there is the containElementSatisfying
matcher:
wift
ct Turtle {
let color: String
turtles: [Turtle] = functionThatReturnsSomeTurtlesInAnyOrder()
his set of matchers passes regardless of whether the array is
{color: "blue"}, {color: "green"}] or [{color: "green"}, {color: "blue"}]:
ct(turtles).to(containElementSatisfying({ turtle in
return turtle.color == "green"
ct(turtles).to(containElementSatisfying({ turtle in
return turtle.color == "blue"
that is a turtle with color 'blue'"))
he second matcher will incorporate the provided string in the error message
hould it fail
bjc
bjective-C
erface Turtle : NSObject
perty (nonatomic, readonly, nonnull) NSString *color;
lementation Turtle
ray<Turtle *> * __nonnull turtles = functionThatReturnsSomeTurtlesInAnyOrder();
his set of matchers passes regardless of whether the array is
{color: "blue"}, {color: "green"}] or [{color: "green"}, {color: "blue"}]:
ct(turtles).to(containElementSatisfying(^BOOL(id __nonnull object) {
return [[turtle color] isEqualToString:@"green"];
ct(turtles).to(containElementSatisfying(^BOOL(id __nonnull object) {
return [[turtle color] isEqualToString:@"blue"];
wift
asses if 'actual' contains 'substring':
ct(actual).to(contain(substring))
asses if 'actual' begins with 'prefix':
ct(actual).to(beginWith(prefix))
asses if 'actual' ends with 'suffix':
ct(actual).to(endWith(suffix))
asses if 'actual' represents the empty string, "":
ct(actual).to(beEmpty())
asses if 'actual' matches the regular expression defined in 'expected':
ct(actual).to(match(expected))
bjc
bjective-C
asses if 'actual' contains 'substring':
ct(actual).to(contain(expected));
asses if 'actual' begins with 'prefix':
ct(actual).to(beginWith(prefix));
asses if 'actual' ends with 'suffix':
ct(actual).to(endWith(suffix));
asses if 'actual' represents the empty string, "":
ct(actual).to(beEmpty());
asses if 'actual' matches the regular expression defined in 'expected':
ct(actual).to(match(expected))
Nimble provides a means to check that all elements of a collection pass a given expectation.
In Swift, the collection must be an instance of a type conforming to
Sequence
.
wift
roviding a custom function:
ct([1, 2, 3, 4]).to(allPass { $0! < 5 })
omposing the expectation with another matcher:
ct([1, 2, 3, 4]).to(allPass(beLessThan(5)))
In Objective-C, the collection must be an instance of a type which implements
the NSFastEnumeration
protocol, and whose elements are instances of a type
which subclasses NSObject
.
Additionally, unlike in Swift, there is no override to specify a custom matcher function.
bjective-C
ct(@[@1, @2, @3, @4]).to(allPass(beLessThan(@5)));
wift
asses if 'actual' contains the 'expected' number of elements:
ct(actual).to(haveCount(expected))
asses if 'actual' does _not_ contain the 'expected' number of elements:
ct(actual).notTo(haveCount(expected))
bjc
bjective-C
asses if 'actual' contains the 'expected' number of elements:
ct(actual).to(haveCount(expected))
asses if 'actual' does _not_ contain the 'expected' number of elements:
ct(actual).notTo(haveCount(expected))
For Swift, the actual value must be an instance of a type conforming to Collection
.
For example, instances of Array
, Dictionary
, or Set
.
For Objective-C, the actual value must be one of the following classes, or their subclasses:
NSArray
,NSDictionary
,NSSet
, orNSHashTable
.wift
testNotification = Notification(name: "Foo", object: nil)
asses if the closure in expect { ... } posts a notification to the default
otification center.
ct {
NotificationCenter.default.postNotification(testNotification)
(postNotifications(equal([testNotification]))
asses if the closure in expect { ... } posts a notification to a given
otification center
notificationCenter = NotificationCenter()
ct {
notificationCenter.postNotification(testNotification)
(postNotifications(equal([testNotification]), fromNotificationCenter: notificationCenter))
This matcher is only available in Swift.
wift
asses if actual is either less than 10 or greater than 20
ct(actual).to(satisfyAnyOf(beLessThan(10), beGreaterThan(20)))
an include any number of matchers -- the following will pass
*be careful** -- too many matchers can be the sign of an unfocused test
ct(6).to(satisfyAnyOf(equal(2), equal(3), equal(4), equal(5), equal(6), equal(7)))
n Swift you also have the option to use the || operator to achieve a similar function
ct(82).to(beLessThan(50) || beGreaterThan(80))
bjc
bjective-C
asses if actual is either less than 10 or greater than 20
ct(actual).to(satisfyAnyOf(beLessThan(@10), beGreaterThan(@20)))
an include any number of matchers -- the following will pass
*be careful** -- too many matchers can be the sign of an unfocused test
ct(@6).to(satisfyAnyOf(equal(@2), equal(@3), equal(@4), equal(@5), equal(@6), equal(@7)))
Note: This matcher allows you to chain any number of matchers together. This provides flexibility,
but if you find yourself chaining many matchers together in one test, consider whether you
could instead refactor that single test into multiple, more precisely focused tests for
better coverage.
wift
asses if .succeeded is returned from the closure
ct({
guard case .enumCaseWithAssociatedValueThatIDontCareAbout = actual else {
return .failed(reason: "wrong enum case")
}
return .succeeded
o(succeed())
asses if .failed is returned from the closure
ct({
guard case .enumCaseWithAssociatedValueThatIDontCareAbout = actual else {
return .failed(reason: "wrong enum case")
}
return .succeeded
otTo(succeed())
The String
provided with .failed()
is shown when the test fails.
When using toEventually()
be careful not to make state changes or run process intensive code since this closure will be ran many times.
In Nimble, matchers are Swift functions that take an expected
value and return a Predicate
closure. Take equal
, for example:
wift
ic func equal<T: Equatable>(expectedValue: T?) -> Predicate<T> {
Can be shortened to:
Predicate { actual in ... }
But shown with types here for clarity.
turn Predicate { (actual: Expression<T>) throws -> PredicateResult in
let msg = ExpectationMessage.expectedActualValueTo("equal <\(expectedValue)>")
if let actualValue = try actualExpression.evaluate() {
return PredicateResult(
bool: actualValue == expectedValue!,
message: msg
)
} else {
return PredicateResult(
status: .fail,
message: msg.appendedBeNilHint()
)
}
The return value of a Predicate
closure is a PredicateResult
that indicates
whether the actual value matches the expectation and what error message to
display on failure.
The actual
equal
matcher function does not match whenexpected
are nil; the example above has been edited for brevity.
Since matchers are just Swift functions, you can define them anywhere: at the top of your test file, in a file shared by all of your tests, or in an Xcode project you distribute to others.
If you write a matcher you think everyone can use, consider adding it to Nimble's built-in set of matchers by sending a pull request! Or distribute it yourself via GitHub.
For examples of how to write your own matchers, just check out the
Matchers
directory
to see how Nimble's built-in set of matchers are implemented. You can
also check out the tips below.
PredicateResult
is the return struct that Predicate
return to indicate
success and failure. A PredicateResult
is made up of two values:
PredicateStatus
and ExpectationMessage
.
Instead of a boolean, PredicateStatus
captures a trinary set of values:
wift
ic enum PredicateStatus {
he predicate "passes" with the given expression
g - expect(1).to(equal(1))
matches
he predicate "fails" with the given expression
g - expect(1).toNot(equal(1))
doesNotMatch
he predicate never "passes" with the given expression, even if negated
g - expect(nil as Int?).toNot(equal(1))
fail
..
Meanwhile, ExpectationMessage
provides messaging semantics for error reporting.
wift
ic indirect enum ExpectationMessage {
mits standard error message:
g - "expected to <string>, got <actual>"
expectedActualValueTo(/* message: */ String)
llows any free-form message
g - "<string>"
fail(/* message: */ String)
..
Predicates should usually depend on either .expectedActualValueTo(..)
or
.fail(..)
when reporting errors. Special cases can be used for the other enum
cases.
Finally, if your Predicate utilizes other Predicates, you can utilize
.appended(details:)
and .appended(message:)
methods to annotate an existing
error with more details.
A common message to append is failing on nils. For that, .appendedBeNilHint()
can be used.
actualExpression
is a lazy, memoized closure around the value provided to the
expect
function. The expression can either be a closure or a value directly
passed to expect(...)
. In order to determine whether that value matches,
custom matchers should call actualExpression.evaluate()
:
wift
ic func beNil<T>() -> Predicate<T> {
// Predicate.simpleNilable(..) automatically generates ExpectationMessage for
// us based on the string we provide to it. Also, the 'Nilable' postfix indicates
// that this Predicate supports matching against nil actualExpressions, instead of
// always resulting in a PredicateStatus.fail result -- which is true for
// Predicate.simple(..)
return Predicate.simpleNilable("be nil") { actualExpression in
let actualValue = try actualExpression.evaluate()
return PredicateStatus(bool: actualValue == nil)
}
In the above example, actualExpression
is not nil
– it is a closure
that returns a value. The value it returns, which is accessed via the
evaluate()
method, may be nil
. If that value is nil
, the beNil
matcher function returns true
, indicating that the expectation passed.
Using Swift's generics, matchers can constrain the type of the actual value
passed to the expect
function by modifying the return type.
For example, the following matcher, haveDescription
, only accepts actual
values that implement the Printable
protocol. It checks their description
against the one provided to the matcher function, and passes if they are the same:
wift
ic func haveDescription(description: String) -> Predicate<Printable?> {
turn Predicate.simple("have description") { actual in
return PredicateStatus(bool: actual.evaluate().description == description)
When using Predicate.simple(..)
or Predicate.simpleNilable(..)
, Nimble
outputs the following failure message when an expectation fails:
here `message` is the first string argument and
actual` is the actual value received in `expect(..)`
ected to \(message), got <\(actual)>"
You can customize this message by modifying the way you create a Predicate
.
For slightly more complex error messaging, receive the created failure message
with Predicate.define(..)
:
wift
ic func equal<T: Equatable>(_ expectedValue: T?) -> Predicate<T> {
return Predicate.define("equal <\(stringify(expectedValue))>") { actualExpression, msg in
let actualValue = try actualExpression.evaluate()
let matches = actualValue == expectedValue && expectedValue != nil
if expectedValue == nil || actualValue == nil {
if expectedValue == nil && actualValue != nil {
return PredicateResult(
status: .fail,
message: msg.appendedBeNilHint()
)
}
return PredicateResult(status: .fail, message: msg)
}
return PredicateResult(bool: matches, message: msg)
}
In the example above, msg
is defined based on the string given to
Predicate.define
. The code looks akin to:
wift
msg = ExpectationMessage.expectedActualValueTo("equal <\(stringify(expectedValue))>")
To fully customize the behavior of the Predicate, use the overload that expects
a PredicateResult
to be returned.
Along with PredicateResult
, there are other ExpectationMessage
enum values you can use:
ic indirect enum ExpectationMessage {
mits standard error message:
g - "expected to <message>, got <actual>"
expectedActualValueTo(/* message: */ String)
llows any free-form message
g - "<message>"
fail(/* message: */ String)
mits standard error message with a custom actual value instead of the default.
g - "expected to <message>, got <actual>"
expectedCustomValueTo(/* message: */ String, /* actual: */ String)
mits standard error message without mentioning the actual value
g - "expected to <message>"
expectedTo(/* message: */ String, /* actual: */ String)
..
For matchers that compose other matchers, there are a handful of helper functions to annotate messages.
appended(message: String)
is used to append to the original failure message:
roduces "expected to be true, got <actual> (use beFalse() for inverse)"
ppended message do show up inline in Xcode.
ectedActualValueTo("be true").appended(message: " (use beFalse() for inverse)")
For a more comprehensive message that spans multiple lines, use
appended(details: String)
instead:
roduces "expected to be true, got <actual>\n\nuse beFalse() for inverse\nor use beNil()"
etails do not show inline in Xcode, but do show up in test logs.
ectedActualValueTo("be true").appended(details: "use beFalse() for inverse\nor use beNil()")
To use a custom matcher written in Swift from Objective-C, you'll have
to extend the NMBObjCMatcher
class, adding a new class method for your
custom matcher. The example below defines the class method
+[NMBObjCMatcher beNilMatcher]
:
wift
nsion NMBObjCMatcher {
blic class func beNilMatcher() -> NMBObjCMatcher {
return NMBObjCMatcher { actualBlock, failureMessage, location in
let block = ({ actualBlock() as NSObject? })
let expr = Expression(expression: block, location: location)
return beNil().matches(expr, failureMessage: failureMessage)
}
The above allows you to use the matcher from Objective-C:
bjective-C
ct(actual).to([NMBObjCMatcher beNilMatcher]());
To make the syntax easier to use, define a C function that calls the class method:
bjective-C
DATION_EXPORT id<NMBMatcher> beNil() {
turn [NMBObjCMatcher beNilMatcher];
nil
in Objective-C MatchersWhen supporting Objective-C, make sure you handle nil
appropriately.
Like Cedar,
most matchers do not match with nil. This is to bring prevent test
writers from being surprised by nil
values where they did not expect
them.
Nimble provides the beNil
matcher function for test writer that want
to make expectations on nil
objects:
bjective-C
ct(nil).to(equal(nil)); // fails
ct(nil).to(beNil()); // passes
If your matcher does not want to match with nil, you use NonNilMatcherFunc
and the canMatchNil
constructor on NMBObjCMatcher
. Using both types will
automatically generate expected value failure messages when they're nil.
ic func beginWith<S: Sequence, T: Equatable where S.Iterator.Element == T>(startingElement: T) -> NonNilMatcherFunc<S> {
return NonNilMatcherFunc { actualExpression, failureMessage in
failureMessage.postfixMessage = "begin with <\(startingElement)>"
if let actualValue = actualExpression.evaluate() {
var actualGenerator = actualValue.makeIterator()
return actualGenerator.next() == startingElement
}
return false
}
nsion NMBObjCMatcher {
public class func beginWithMatcher(expected: AnyObject) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
let actual = actualExpression.evaluate()
let expr = actualExpression.cast { $0 as? NMBOrderedCollection }
return beginWith(expected).matches(expr, failureMessage: failureMessage)
}
}
Previously (<7.0.0
), Nimble supported matchers via the following types:
Matcher
NonNilMatcherFunc
MatcherFunc
All of those types have been replaced by Predicate
. While migrating can be a
lot of work, Nimble currently provides several steps to aid migration of your
custom matchers:
.predicate
Nimble provides an extension to the old types that automatically naively
converts those types to the newer Predicate
.
wift
ic func beginWith<S: Sequence, T: Equatable where S.Iterator.Element == T>(startingElement: T) -> Predicate<S> {
return NonNilMatcherFunc { actualExpression, failureMessage in
failureMessage.postfixMessage = "begin with <\(startingElement)>"
if let actualValue = actualExpression.evaluate() {
var actualGenerator = actualValue.makeIterator()
return actualGenerator.next() == startingElement
}
return false
}.predicate
This is the simpliest way to externally support Predicate
which allows easier
composition than the old Nimble matcher interface, with minimal effort to change.
Predicate
Type with Old Matcher ConstructorThe second most convenient step is to utilize special constructors that
Predicate
supports that closely align to the constructors of the old Nimble
matcher types.
wift
ic func beginWith<S: Sequence, T: Equatable where S.Iterator.Element == T>(startingElement: T) -> Predicate<S> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
failureMessage.postfixMessage = "begin with <\(startingElement)>"
if let actualValue = actualExpression.evaluate() {
var actualGenerator = actualValue.makeIterator()
return actualGenerator.next() == startingElement
}
return false
}
This allows you to completely drop the old types from your code, although the intended behavior may alter slightly to what is desired.
Predicate
Type with Preferred ConstructorFinally, you can convert to the native Predicate
format using one of the
constructors not used to assist in the migration.
Nimble 7 introduces Predicate
but will support the old types with warning
deprecations. A couple major releases of Nimble will remain backwards
compatible with the old matcher api, although new features may not be
backported.
The deprecating plan is a 3 major versions removal. Which is as follows:
Predicate
API, deprecation warning for old matcher APIs.
(Nimble v7.x.x
).predicate
,
Predicate
-constructors with similar arguments to old API). (Nimble
v8.x.x
)v9.x.x
)Nimble can be used on its own, or in conjunction with its sister project, Quick. To install both Quick and Nimble, follow the installation instructions in the Quick Documentation.
Nimble can currently be installed in one of two ways: using CocoaPods, or with git submodules.
To use Nimble as a submodule to test your macOS, iOS or tvOS applications, follow these 4 easy steps:
For more detailed instructions on each of these steps, read How to Install Quick. Ignore the steps involving adding Quick to your project in order to install just Nimble.
To use Nimble in CocoaPods to test your macOS, iOS or tvOS applications, add
Nimble to your podfile and add the `use_frameworks!
` line to enable Swift
support for CocoaPods.
form :ios, '8.0'
ce 'https://github.com/CocoaPods/Specs.git'
atever pods you need for your app go here
et 'YOUR_APP_NAME_HERE_Tests', :exclusive => true do
e_frameworks!
d 'Nimble', '~> 6.0.0'
Finally run pod install
.
Nimble is integrated with XCTest to allow it work well when used in Xcode test bundles, however it can also be used in a standalone app. After installing Nimble using one of the above methods, there are two additional steps required to make this work.
NimbleAssertionHandler
variable. For example:s MyAssertionHandler : AssertionHandler {
func assert(assertion: Bool, message: FailureMessage, location: SourceLocation) {
if (!assertion) {
print("Expectation failed: \(message.stringValue)")
}
}
omewhere before you use any assertions
leAssertionHandler = MyAssertionHandler()
${SWIFT_STDLIB_TOOL_DESTINATION_DIR}/libswiftXCTest.dylib"
You can now use Nimble assertions in your code and handle failures as you see fit.