Quick is a behavior-driven development framework for Swift and Objective-C. Inspired by RSpec, Specta, and Ginkgo.
import Quick
import Nimble
class TableOfContentsSpec: QuickSpec {
override func spec() {
describe("the table of contents below") {
it("has everything you need to get started") {
let sections = TableOfContents().sections
expect(sections).to.contain("Quick Core")
expect(sections).to.contain("Quick Expectations")
expect(sections).to.contain("How to Install Quick")
}
context("if it doesn't have what you're looking for") {
it("needs to be updated") {
let you = You(awesome: true)
expect{you.submittedAnIssue}.will.beTrue()
}
}
}
}
}
- Quick: Examples and Example Groups
- Nimble: Assertions Using
expect(...).to
- How to Install Quick
- How to Install Quick File Templates
- Who Uses Quick
- License
Quick uses a special syntax that allows me to define examples and example groups.
Examples use assertions to demonstrate how code should behave. These are like "tests" in XCTest.
Quick allows me to define examples using it
. it
also takes a string,
which serves as a description of the example.
Below, I specify examples of how my Dolphin
class should behave.
When I create a new dolphin, it should be smart and friendly.
// Swift
import Quick
import Nimble
class DolphinSpec: QuickSpec {
override func spec() {
it("is friendly") {
expect(Dolphin().isFriendly).to.beTrue()
}
it("is smart") {
expect(Dolphin().isSmart).to.beTrue()
}
}
}
// Objective-C
#import <Quick/Quick.h>
#import <Nimble/Nimble.h>
QuickSpecBegin(DolphinSpec)
qck_it(@"is friendly", ^{
[nmb_expect(@([[Dolphin new] isFriendly])).to beTrue];
});
qck_it(@"is smart", ^{
[nmb_expect(@([[Dolphin new] isSmart])).to beTrue];
});
QuickSpecEnd
Descriptions can use any character, including characters from languages besides English, or even emoji! ✌️ 😎
Example groups are logical groupings of examples. By grouping similar examples together, we can share setup and teardown code between them.
Let's say I want to specify the behavior of my Dolphin
class's click
method (or, in other words, I want to test the method works).
I can group all of the tests for click
using describe
. Grouping
similar examples together makes my spec easier to read.
// Swift
import Quick
import Nimble
class DolphinSpec: QuickSpec {
override func spec() {
describe("a dolphin") {
describe("its click") {
it("is loud") {
let click = Dolphin().click()
expect(click.isLoud).to.beTrue()
}
it("has a high frequency") {
let click = Dolphin().click()
expect(click.hasHighFrequency).to.beTrue()
}
}
}
}
}
// Objective-C
#import <Quick/Quick.h>
#import <Nimble/Nimble.h>
QuickSpecBegin(DolphinSpec)
qck_describe(@"a dolphin", ^{
qck_describe(@"its click", ^{
qck_it(@"is loud", ^{
Click *click = [[Dolphin new] click];
[nmb_expect(@(click.isLoud)).to beTrue];
});
qck_it(@"has a high frequency", ^{
Click *click = [[Dolphin new] click];
[nmb_expect(@(click.hasHighFrequency)).to beTrue];
});
});
});
QuickSpecEnd
Besides making the intention of my examples clearer, example groups
like describe
allow me to share setup and teardown code among my
examples.
Using beforeEach
, I can create a new instance of a dolphin and its
click before each one of my examples. This ensures that both are in a
"fresh" state for every example.
// Swift
import Quick
import Nimble
class DolphinSpec: QuickSpec {
override func spec() {
describe("a dolphin") {
var dolphin: Dolphin?
beforeEach {
dolphin = Dolphin()
}
describe("its click") {
var click: Click?
beforeEach {
click = dolphin!.click()
}
it("is loud") {
expect(click!.isLoud).to.beTrue()
}
it("has a high frequency") {
expect(click!.hasHighFrequency).to.beTrue()
}
}
}
}
}
// Objective-C
#import <Quick/Quick.h>
#import <Nimble/Nimble.h>
QuickSpecBegin(DolphinSpec)
qck_describe(@"a dolphin", ^{
__block Dolphin *dolphin = nil;
qck_beforeEach(^{
dolphin = [Dolphin new];
});
qck_describe(@"its click", ^{
__block Click *click = nil;
qck_beforeEach(^{
click = [dolphin click];
});
qck_it(@"is loud", ^{
[nmb_expect(@(click.isLoud)).to beTrue];
});
qck_it(@"has a high frequency", ^{
[nmb_expect(@(click.hasHighFrequency)).to beTrue];
});
});
});
QuickSpecEnd
Sharing setup like this might not seem like such a big deal with my dolphin example, but for more complicated objects, it saves me a lot of typing!
To execute code after each example, use afterEach
.
Dolphins use clicks for echolocation. When they approach something particularly interesting to them, they release a series of clicks in order to get a better idea of what it is.
I want to show that my click
method behaves differently in different
circumstances. Normally, the dolphin just clicks once. But when the dolphin
is close to something interesting, it clicks several times.
I can express this in my tests by using context
: one context
for the
normal case, and one context
for when the dolphin is close to
something interesting.
// Swift
import Quick
import Nimble
class DolphinSpec: QuickSpec {
override func spec() {
describe("a dolphin") {
var dolphin: Dolphin?
beforeEach { dolphin = Dolphin() }
describe("its click") {
context("when the dolphin is not near anything interesting") {
it("is only emitted once") {
expect(dolphin!.click().count).to.equal(1)
}
}
context("when the dolphin is near something interesting") {
beforeEach {
let ship = SunkenShip()
Jamaica.dolphinCove.add(ship)
Jamaica.dolphinCove.add(dolphin)
}
it("is emitted three times") {
expect(dolphin!.click().count).to.equal(3)
}
}
}
}
}
}
// Objective-C
#import <Quick/Quick.h>
#import <Nimble/Nimble.h>
QuickSpecBegin(DolphinSpec)
qck_describe(@"a dolphin", ^{
__block Dolphin *dolphin = nil;
qck_beforeEach(^{ dolphin = [Dolphin new]; });
qck_describe(@"its click", ^{
qck_context(@"when the dolphin is not near anything interesting", ^{
qck_it(@"is only emitted once", ^{
[nmb_expect(@([[dolphin click] count])).to nmb_equal:@1];
});
});
qck_context(@"when the dolphin is near something interesting", ^{
qck_beforeEach(^{
[[Jamaica dolphinCove] add:[SunkenShip new]];
[[Jamaica dolphinCove] add:dolphin];
});
qck_it(@"is emitted three times", ^{
[nmb_expect(@([[dolphin click] count])).to nmb_equal:@3];
});
});
});
});
QuickSpecEnd
I can also use pending
in Swift, or qck_pending
in Objective-C, to
denote an example that does not pass yet. Pending blocks are not run,
but are printed out along with the test results.
For example, I haven't yet implemented the click
method to emit a
series of clicks when the dolphin is near something interesting. So I
will mark that example group as pending for now:
// Swift
pending("when the dolphin is near something interesting") {
// ...none of the code in this closure will be run.
}
// Objective-C
qck_pending(@"when the dolphin is near something interesting", ^{
// ...none of the code in this closure will be run.
});
I sometimes need to perform some setup before any of my examples are run. Let's say I maintain a database that keeps track of everything in the ocean. I want to create a new test database before any of my examples run. I also want to get rid of that database once my examples are finished running.
Quick allows me to do this by using beforeSuite
and afterSuite
.
// Swift
import Quick
class DolphinSpec: QuickSpec {
override func spec() {
beforeSuite {
OceanDatabase.createDatabase(name: "test.db")
OceanDatabase.connectToDatabase(name: "test.db")
}
afterSuite {
OceanDatabase.teardownDatabase(name: "test.db")
}
describe("a dolphin") {
// ...
}
}
}
// Objective-C
#import <Quick/Quick.h>
QuickSpecBegin(DolphinSpec)
qck_beforeSuite(^{
[OceanDatabase createDatabase:@"test.db"];
[OceanDatabase connectToDatabase:@"test.db"];
});
qck_afterSuite(^{
[OceanDatabase teardownDatabase:@"test.db"];
});
qck_describe(@"a dolphin", ^{
// ...
});
QuickSpecEnd
I can specify as many beforeSuite
and afterSuite
as I like. All
beforeSuite
will be executed before any tests, and all afterSuite
will be executed after all tests are finished. There's no guarantee as
to what order they will be executed in, however.
I sometimes need to write one set of specifications, then apply those specifications to several test objects.
For example, let's say I have a protocol called Edible
. When a dolphin
eats something Edible
, the dolphin becomes happy.
Mackerel
and Cod
are both edible, and I'd like to specify that
dolphins are happy to eat either one.
I can define a set of "shared examples" for "something edible". Then, I can specify that both mackerel and cod behave like "something edible":
// Swift
import Quick
import Nimble
class EdibleSharedExamples: QuickSharedExampleGroups {
override class func sharedExampleGroups() {
sharedExamples("something edible") { (sharedExampleContext: SharedExampleContext) in
it("makes dolphins happy") {
let dolphin = Dolphin(happy: false)
let edible = sharedExampleContext()["edible"]
dolphin.eat(edible)
expect(dolphin.isHappy).to.beTrue()
}
}
}
}
class MackerelSpec: QuickSpec {
override func spec() {
var mackerel: Mackerel! = nil
beforeEach {
mackerel = Mackerel()
}
itBehavesLike("something edible") { ["edible": mackerel] }
}
}
class CodSpec: QuickSpec {
override func spec() {
var cod: Cod! = nil
beforeEach {
cod = Cod()
}
itBehavesLike("something edible") { ["edible": cod] }
}
}
Shared examples can include any number of it
, context
, and
describe
blocks. They save me a lot of typing when I want to run
the same tests against several different kinds of objects.
If I don't need any additional context, I can simply use closures that take no parameters. This might be useful when testing some sort of global state:
// Swift
import Quick
sharedExamplesFor("everything under the sea") {
// ...
}
itBehavesLike("everything under the sea")
I can use Quick to define examples and example groups. Within those examples, I can make expectations using Nimble, Quick's sister project.
Nimble expectations use the expect(...).to
syntax:
// Swift
import Nimble
expect(person.greeting).to.equal("Oh, hi.")
expect(person.greeting).notTo.equal("Hello!")
// Objective-C
#import <Nimble/Nimble.h>
[nmb_expect(person.greeting).to nmb_equal:@"Oh, hi."];
[nmb_expect(person.greeting).notTo nmb_equal:@"Hello!"];
Nimble includes matchers that test whether the subject of an expectation is true, or equal to something, or whether it contains a specific element:
// Swift
import Nimble
expect(person.isHappy).to.beTrue()
expect(person.greeting).to.equal("Hello!")
expect(person.hopes).to.contain("winning the lottery")
// Objective-C
#import <Nimble/Nimble.h>
[nmb_expect(@(person.isHappy)).to beTrue];
[nmb_expect(person.greeting).to nmb_equal:@"Hello!"];
[nmb_expect(person.hopes).to nmb_contain:@"winning the lottery"];
Equal
matches if two objects are equal. It tests for equality using
the ==
operator:
expect("dolphin").to.equal("dolphin")
expect("dolphin").toNot.equal("sea turtle")
[nmb_expect(@"dolphin").to nmb_equal:@"dolphin"];
[nmb_expect(@"dolphin").toNot nmb_equal:@"sea turtle"];
BeIdenticalTo
matches if two objects are the same object. It tests for
identity using the ===
operator:
let kind = "bottlenose dolphins"
expect(kind).to.beIdenticalTo(kind)
expect(kind).toNot.beIdenticalTo("bottlenose dolphins")
NSString *kind = @"bottlenose dolphins"
[nmb_expect(kind).to nmb_beIdenticalTo:kind];
[nmb_expect(kind).toNot nmb_beIdenticalTo:@"bottlenose dolphins"];
Note that in the above example, beIdenticalTo()
matches when comparing
the same string object (kind
), but doesn't match for two different string
objects--even though the strings themselves are equal.
BeNil
acts just like the Equal
matcher, but it tests whether the
subject of the expectation is equal to nil
:
expect(nil).to.beNil()
expect("dolphin").toNot.beNil()
[nmb_expect(nil).to beNil];
[nmb_expect(@"dolphin").toNot beNil];
BeTrue
matches if the subject of the expectation is equal to true
:
expect(true).to.beTrue()
expect(false).toNot.beTrue()
expect(10).toNot.beTrue()
[nmb_expect(@YES).to beTrue];
[nmb_expect(@NO).toNot beTrue];
[nmb_expect(@10).to beTrue];
Note that although 10
above is a "truthy" value, it is not equal to
the boolean true
, so beTrue()
does not match.
NOTE: In Objective-C,
@1
and@YES
are equal, so the expectation[nmb_expect(@1).to beTrue]
passes.
Similarly to BeTrue
, BeFalse
matches if the subject of the
expectation is equal to false
:
expect(false).to.beFalse()
expect(true).toNot.beFalse()
expect(nil).toNot.beFalse)
[nmb_expect(@NO).to beFalse];
[nmb_expect(@YES).toNot beFalse];
[nmb_expect(nil).toNot beFalse];
These matchers compare the subject to arbitrary numbers. They only match if both the subject and the given value are numbers, and those numbers meet the given conditions:
expect(10).to.beLessThan(11)
expect(10).to.beLessThanOrEqualTo(10)
expect(10).to.beGreaterThanOrEqualTo(10)
expect(10).to.beGreaterThan(9)
[nmb_expect(@10).to nmb_beLessThan(@11)];
[nmb_expect(@10).to nmb_beLessThanOrEqualTo(@10)];
[nmb_expect(@10).to nmb_beGreaterThanOrEqualTo(@10)];
[nmb_expect(@10).to nmb_beGreaterThan(@9)];
These matchers apply to collections such as arrays or sets. Contain
matches if the array or set contains the specified element:
expect([1, 2, 3]).to.contain(1)
expect([1, 2, 3]).toNot.contain(4)
[nmb_expect((@[@1, @2, @3])).to nmb_contain:@1];
[nmb_expect((@[@1, @2, @3])).to nmb_contain:@4];
Contain
can also be used on strings. It matches if the string contains
the given substring:
expect("blowfish").to.contain("fish")
expect("dolphin").toNot.contain("fish")
[nmb_expect(@"blowfish").to nmb_contain:@"fish"];
[nmb_expect(@"dolphin").to nmb_contain:@"fish"];
BeEmpty
matches if the given array or set contains no elements, or if
the given string is an empty string (""
):
expect([]).to.beEmpty()
expect([1, 2, 3]).toNot.beEmpty()
expect("").to.beEmpty()
[nmb_expect((@[])).to beEmpty];
[nmb_expect((@[@1, @2, @3])).toNot beEmpty];
[nmb_expect(@"").to beEmpty];
Quick also allows me to make expectations on closures. For example, I can specify that a block of code raises an exception. I can also specify it raises an exception with a specific name, reason, or userInfo dictionary:
expect{penguin.fly()}.to.raise()
expect{penguin.fly()}.to.raise(NSInternalInconsistencyException)
expect{penguin.fly()}.to.raise(NSInternalInconsistencyException, reason: "Penguins can't fly!")
expect{penguin.fly()}.to.raise(NSInternalInconsistencyException, reason: "Penguins can't fly!", userInfo: ["url": "https://www.youtube.com/watch?v=6JsZbSzMi08"])
[nmb_expect([penguin fly]).to nmb_raise];
[nmb_expect([penguin fly]).to nmb_raiseWithName:NSInternalInconsistencyException];
[nmb_expect([penguin fly]).to nmb_raiseWithName:NSInternalInconsistencyException
reason:@"Penguins can't fly!"];
[nmb_expect([penguin fly]).to nmb_raiseWithName:NSInternalInconsistencyException
reason:@"Penguins can't fly!"
userInfo:@{@"url": @"https://www.youtube.com/watch?v=6JsZbSzMi08"}];
When passing an optional to an expectation in Swift, there's no need to
unwrap the variable using a trailing !
: Nimble does that for me.
var optVal: Int?
expect(optVal).to.beNil()
optVal = 123
expect(optVal).toNot.beNil()
expect(optVal).to.equal(123)
Nimble also allows for asynchronous expectations, by wrapping the subject in braces instead of parentheses. This allows the subject to be evaluated as a closure. Below is an example of a subject who knows only hunger, and never satisfaction:
// Swift
import Nimble
expect{person.isHungry}.will.beTrue()
expect{person.isSatisfied}.willNot.beTrue()
// Objective-C
#import <Nimble/Nimble.h>
[nmb_expectBlock(^{ return @(person.isHungry); }).will beTrue];
[nmb_expectBlock(^{ return @(person.isSatisfied); }).willNot beTrue];
Asynchronous expectations time out after one second by default. I can
extend this default by using willBefore
. The following times out after 3
seconds:
// Swift
import Nimble
expect{person!.isHungry}.willBefore(3).beTrue()
expect{person!.isSatisfied}.willNotBefore(3).beTrue()
// Objective-C
#import <Nimble/Nimble.h>
[[nmb_expectBlock(^{ return @(person.isHungry); }) willBefore:3] beTrue];
[[nmb_expectBlock(^{ return @(person.isSatisfied); }) willNotBefore:3] beTrue];
This module is beta software, and can only run using the latest, beta version of Xcode.
Quick provides the syntax to define examples and example groups. Nimble
provides the expect(...).to
assertion syntax. You may either one, or
both, in your tests.
To use Quick and Nimble to test your iOS or OS X applications, follow these 4 easy steps:
- Clone the repository
- Add
Quick.xcodeproj
andNimble.xcodeproj
to your test target - Link
Quick.framework
andNimble.framework
- Start writing specs!
An example project with this complete setup is available in the
Examples
directory.
git clone [email protected]:modocache/Quick.git
Right-click on the group containing your application's tests and
select Add Files To YourApp...
.
Next, select Quick.xcodeproj
, which you downloaded in step 1.
Once you've added the Quick project, you should see it in Xcode's project navigator, grouped with your tests.
Follow the same steps for Nimble.xcodeproj
.
Link the Quick.framework
during your test target's
Link Binary with Libraries
build phase. You should see two
Quick.frameworks
; one is for OS X, and the other is for iOS.
Do the same for the Nimble.framework
.
If you run into any problems, please file an issue.
The Quick repository includes file templates for both Swift and Objective-C specs.
Quick templates can be installed via Alcatraz, a package manager for Xcode. Just search for the templates from the Package Manager window.
To manually install the templates, just clone the repository and
run the templates:install
rake task:
$ git clone [email protected]:modocache/Quick.git
$ rake templates:install
Uninstalling is easy, too:
$ rake templates:uninstall
Add an issue or tweet if you'd like to be added to this list.
MIT license. See the LICENSE
file for details.