Quick is a behavior-driven development framework for Swift and Objective-C. Inspired by RSpec, Specta, and Ginkgo.
import Quick
class TableOfContentsSpec: QuickSpec {
override func exampleGroups() {
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
- 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
class DolphinSpec: QuickSpec {
override func exampleGroups() {
it("is friendly") {
expect(Dolphin().isFriendly).to.beTrue()
}
it("is smart") {
expect(Dolphin().isSmart).to.beTrue()
}
}
}
// Objective-C
#import <Quick/Quick.h>
QuickSpecBegin(DolphinSpec)
qck_it(@"is friendly", ^{
XCTAssertTrue([[Dolphin new] isFriendly], @"expected dolphin to be friendly");
});
qck_it(@"is smart", ^{
XCTAssertTrue([[Dolphin new] isSmart], @"expected dolphin to be smart");
});
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
class DolphinSpec: QuickSpec {
override func exampleGroups() {
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>
QuickSpecBegin(DolphinSpec)
qck_describe(@"a dolphin", ^{
qck_describe(@"its click", ^{
qck_it(@"is loud", ^{
Click *click = [[Dolphin new] click];
XCTAssertTrue(click.isLoud, @"expected dolphin click to be loud");
});
qck_it(@"has a high frequency", ^{
Click *click = [[Dolphin new] click];
XCTAssertTrue(click.hasHighFrequency,
@"expected dolphin click to have a high frequency");
});
});
});
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
class DolphinSpec: QuickSpec {
override func exampleGroups() {
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>
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", ^{
XCTAssertTrue(click.isLoud, @"expected dolphin click to be loud");
});
qck_it(@"has a high frequency", ^{
XCTAssertTrue(click.hasHighFrequency,
@"expected dolphin click to have a high frequency");
});
});
});
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
class DolphinSpec: QuickSpec {
override func exampleGroups() {
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>
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", ^{
XCTAssertEqual([[dolphin click] count], 1,
@"expected dolphin click to be emitted once");
});
});
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", ^{
XCTAssertEqual([[dolphin click] count], 3,
@"expected dolphin click to be emitted three times");
});
});
});
});
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 exampleGroups() {
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.
Currently Nimble expectations are only available in Swift. See https://github.com/modocache/Quick/issues/26 for more details.
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:
expect(person.greeting).to.equal("Oh, hi.")
expect(person.greeting).notTo.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:
expect(person.isHappy).to.beTrue()
expect(person.greeting).to.equal("Hello!")
expect(person.hopes).to.contain("winning the lottery")
When passing an optional to an expectation, 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:
expect{person.isHungry}.will.beTrue()
expect{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:
expect{person!.isHungry}.willBefore(3).beTrue()
expect{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.
Add an issue or tweet if you'd like to be added to this list.
MIT license. See the LICENSE
file for details.