Quick is a behavior-driven development framework for Swift and Objective-C. Inspired by RSpec, Specta, and Ginkgo.
// Swift
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: Examples and Example Groups"))
expect(sections).to(contain("Nimble: Assertions using expect(...).to"))
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}.toEventually(beTruthy())
}
}
}
}
}
- Quick: Examples and Example Groups
- Using Quick in Objective-C: The Optional Shorthand Syntax
- Nimble: Assertions Using
expect(...).to
- Testing Swift Code
- Testing UIKit with Quick
- How to Install Quick
- Including Quick in a Git Repository Using Submodules
- How to Install Quick using Beta CocoaPods
- How to Install Quick File Templates
- Configuring Quick
- Who Uses Quick
- License
Quick uses a special syntax to define examples and example groups.
Examples, defined with the it
function, use assertions to demonstrate
how code should behave. These are like "tests" in XCTest.
it
takes two parameters: the name of the example, and a closure.
The examples below specify how the Dolphin
class should behave.
A new dolphin should be smart and friendly:
// Swift
import Quick
import Nimble
class DolphinSpec: QuickSpec {
override func spec() {
it("is friendly") {
expect(Dolphin().isFriendly).to(beTruthy())
}
it("is smart") {
expect(Dolphin().isSmart).to(beTruthy())
}
}
}
// Objective-C
#import <Quick/Quick.h>
#import <Nimble/Nimble.h>
QuickSpecBegin(DolphinSpec)
it(@"is friendly", ^{
expect(@([[Dolphin new] isFriendly])).to(beTruthy());
});
it(@"is smart", ^{
expect(@([[Dolphin new] isSmart])).to(beTruthy());
});
QuickSpecEnd
Descriptions can use any character, including characters from languages besides English, or even emoji! โ๏ธ ๐
Example groups are logical groupings of examples. Example groups can share setup and teardown code.
To specify the behavior of the Dolphin
class's click
method--in
other words, to test the method works--several it
examples can be
grouped together using the describe
function. Grouping similar
examples together makes the 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(beTruthy())
}
it("has a high frequency") {
let click = Dolphin().click()
expect(click.hasHighFrequency).to(beTruthy())
}
}
}
}
}
// Objective-C
#import <Quick/Quick.h>
#import <Nimble/Nimble.h>
QuickSpecBegin(DolphinSpec)
describe(@"a dolphin", ^{
describe(@"its click", ^{
it(@"is loud", ^{
Click *click = [[Dolphin new] click];
expect(@(click.isLoud)).to(beTruthy());
});
it(@"has a high frequency", ^{
Click *click = [[Dolphin new] click];
expect(@(click.hasHighFrequency)).to(beTruthy());
});
});
});
QuickSpecEnd
Example groups don't just make the examples clearer, they're also useful for sharing setup and teardown code among examples in a group.
In the example below, the beforeEach
function is used to create a brand
new instance of a dolphin and its click before each example in the group.
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(beTruthy())
}
it("has a high frequency") {
expect(click!.hasHighFrequency).to(beTruthy())
}
}
}
}
}
// Objective-C
#import <Quick/Quick.h>
#import <Nimble/Nimble.h>
QuickSpecBegin(DolphinSpec)
describe(@"a dolphin", ^{
__block Dolphin *dolphin = nil;
beforeEach(^{
dolphin = [Dolphin new];
});
describe(@"its click", ^{
__block Click *click = nil;
beforeEach(^{
click = [dolphin click];
});
it(@"is loud", ^{
expect(@(click.isLoud)).to(beTruthy());
});
it(@"has a high frequency", ^{
expect(@(click.hasHighFrequency)).to(beTruthy());
});
});
});
QuickSpecEnd
Sharing setup like this might not seem like a big deal with the dolphin example, but for more complicated objects, it saves 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.
The tests need to show that the 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.
This can be expressed using context
functions: 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)
describe(@"a dolphin", ^{
__block Dolphin *dolphin = nil;
beforeEach(^{ dolphin = [Dolphin new]; });
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(^{
[[Jamaica dolphinCove] add:[SunkenShip new]];
[[Jamaica dolphinCove] add:dolphin];
});
it(@"is emitted three times", ^{
expect(@([[dolphin click] count])).to(equal(@3));
});
});
});
});
QuickSpecEnd
For examples that don't pass yet, use pending
. Pending examples
are not run, but are printed out along with the test results.
The example below marks the cases in which the dolphin is close to something interesting as "pending"--perhaps that functionality hasn't been implemented yet, but these tests have been written as reminders that it should be soon:
// Swift
pending("when the dolphin is near something interesting") {
// ...none of the code in this closure will be run.
}
// Objective-C
pending(@"when the dolphin is near something interesting", ^{
// ...none of the code in this closure will be run.
});
Examples and groups can also be marked as pending by using
xdescribe
, xcontext
, and xit
:
// Swift
xdescribe("its click") {
// ...none of the code in this closure will be run.
}
xcontext("when the dolphin is not near anything interesting") {
// ...none of the code in this closure will be run.
}
xit("is only emitted once") {
// ...none of the code in this closure will be run.
}
// Objective-C
xdescribe(@"its click", ^{
// ...none of the code in this closure will be run.
});
xcontext(@"when the dolphin is not near anything interesting", ^{
// ...none of the code in this closure will be run.
});
xit(@"is only emitted once", ^{
// ...none of the code in this closure will be run.
});
Sometimes it helps to focus on only one or a few examples. Running one
or two exmaples is faster than the entire suite, after all. You can
run only one or two by using the fit
function. You can also focus a
group of examples using fdescribe
or fcontext
:
fit("its click") {
// ...only this focused example will be run.
}
it("is only emitted once") {
// ...this example is not focused, and will not be run.
}
fdescribe("when the dolphin is near something interesting") {
// ...examples in this group are also focused, so they'll be run.
}
fit(@"its click", {
// ...only this focused example will be run.
});
it(@"is only emitted once", ^{
// ...this example is not focused, and will not be run.
});
fdescribe(@"when the dolphin is near something interesting", ^{
// ...examples in this group are also focused, so they'll be run.
});
Some test setup needs to be performed before any examples are
run. For these cases, use beforeSuite
and afterSuite
.
In the example below, a database of all the creatures in the ocean is created before any examples are run. That database is torn down once all the examples have finished:
// 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)
beforeSuite(^{
[OceanDatabase createDatabase:@"test.db"];
[OceanDatabase connectToDatabase:@"test.db"];
});
afterSuite(^{
[OceanDatabase teardownDatabase:@"test.db"];
});
describe(@"a dolphin", ^{
// ...
});
QuickSpecEnd
You can specify as many
beforeSuite
andafterSuite
as you like. AllbeforeSuite
closures will be executed before any tests run, and allafterSuite
closures will be executed after all the tests are finished. There is no guarantee as to what order these closures will be executed in.
In some cases, the same set of specifications apply to multiple objects.
For example, consider a protocol called Edible
. When a dolphin
eats something Edible
, the dolphin becomes happy. Mackerel
and
Cod
are both edible. Quick allows you to easily test that a dolphin is
happy to eat either one.
The example below defines a set of "shared examples" for "something edible", and specifies that both mackerel and cod behave like "something edible":
// Swift
import Quick
import Nimble
class EdibleSharedExamplesConfiguration: QuickConfiguration {
override class func configure(configuration: Configuration) {
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(beTruthy())
}
}
}
}
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] }
}
}
// Objective-C
#import <Quick/Quick.h>
#import <Nimble/Nimble.h>
QuickConfigurationBegin(EdibleSharedExamplesConfiguration)
+ (void)configure:(Configuration *configuration) {
sharedExamples(@"something edible", ^(QCKDSLSharedExampleContext exampleContext) {
it(@"makes dolphins happy") {
Dolphin *dolphin = [[Dolphin alloc] init];
dolphin.happy = NO;
id<Edible> edible = exampleContext()[@"edible"];
[dolphin eat:edible];
expect(dolphin.isHappy).to(beTruthy())
}
});
}
QuickConfigurationEnd
QuickSpecBegin(MackerelSpec)
__block Mackerel *mackerel = nil;
beforeEach(^{
mackerel = [[Mackerel alloc] init];
});
itBehavesLike(@"someting edible", ^{ return @{ @"edible": mackerel }; });
QuickSpecEnd
QuickSpecBegin(CodSpec)
__block Mackerel *cod = nil;
beforeEach(^{
cod = [[Cod alloc] init];
});
itBehavesLike(@"someting edible", ^{ return @{ @"edible": cod }; });
QuickSpecEnd
Shared examples can include any number of it
, context
, and
describe
blocks. They save a lot of typing when running
the same tests against several different kinds of objects.
In some cases, you won't need any additional context. In Swift, you can
simply use sharedExampleFor
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")
In Objective-C, you'll have to pass a block that takes a
QCKDSLSharedExampleContext
, even if you don't plan on using that argument. Sorry, but that's the way the cookie crumbles! :cookie: :bomb:
You can also focus shared examples using the fitBehavesLike
function.
Quick works equally well in both Swift and Objective-C.
Importing Quick in an Objective-C file defines macros named it
and
itShouldBehaveLike
, as well as functions like context()
, describe()
, etc.
If the project you are testing also defines symbols with these names, you may
encounter confusing build failures. In that case, you can avoid namespace
collision by turning off Quick's optional "shorthand" syntax:
#define QUICK_DISABLE_SHORT_SYNTAX 1
#import <Quick/Quick.h>
QuickSpecBegin(DolphinSpec)
// ...
QuickSpecEnd
You must define the QUICK_DISABLE_SHORT_SYNTAX
macro before
importing the Quick header.
The Swift stdlib will not be linked into your test target, and thus Quick will fail to execute properly, if you test target does not contain at least one Swift file. If it does not, your tests will exit prematurely with the following error:
*** Test session exited(82) without checking in. Executable cannot be
loaded for some other reason, such as a problem with a library it
depends on or a code signature/entitlements mismatch.
To fix the problem, add a blank file called SwiftSpec.swift
to your test target:
// SwiftSpec.swift
import Quick
For more details on this issue, see Quick#164.
Quick provides an easy language to define examples and example groups. Within those examples, Nimble provides a simple language to define expectations--that is, to assert that code behaves a certain way, and to display a test failure if it doesn't.
Nimble expectations use the expect(...).to
syntax:
// Swift
import Nimble
expect(person.greeting).to(equal("Oh, hi."))
expect(person.greeting).notTo(equal("Hello!"))
expect(person.isHappy).toEventually(beTruthy())
// Objective-C
#import <Nimble/Nimble.h>
expect(person.greeting).to(equal(@"Oh, hi."));
expect(person.greeting).notTo(equal(@"Hello!"));
expect(@(person.isHappy)).toEventually(beTruthy());
You can find much more detailed documentation on Nimble, including a full set of available matchers and details on how to perform asynchronous tests, in the project's README.
In order to test code written in Swift, you'll need to do three things:
- Set "defines module" in your
.xcodeproj
toYES
. - Mark any class/method/function you want to test
public
, since onlypublic
symbols are exported. import YourAppModuleName
in your unit tests.
Some developers advocate adding Swift source files to your test target. However, this leads to subtle, hard-to-diagnose errors, and is not recommended.
Quick can be used for testing UIKit interaction as well. Say, for example, we have a DolphinTableViewController
that displays one cell with label Bottlenose
. We want to test that the cell gets displayed when the view is loaded. Additionally, we would like to delete the row upon selecting it. An approach might be:
// Swift
import UIKit
import Quick
import Nimble
class DolphinTableViewControllerSpecs: QuickSpec {
override func spec() {
var viewController: DolphinTableViewController!
beforeEach {
viewController = DolphinTableViewController()
}
describe("viewDidLoad") {
beforeEach {
// Accessing the view property causes the UIKit framework to trigger the necessary methods to render the view.
viewController.view
}
it("loads the table view with one cell") {
let tableView = viewController.tableView
var indexPath = NSIndexPath(forRow: 0, inSection: 0)
var cell = viewController.tableView(tableView, cellForRowAtIndexPath: indexPath)
expect(cell.textLabel?.text).to(equal("Bottlenose"))
}
}
describe("didSelectRowAtIndexPath") {
beforeEach {
// Causes the UIKit framework to trigger the necessary methods to render the view and perform viewWillAppear: and viewDidAppear: callbacks
viewController.beginAppearanceTransition(true, animated: false)
viewController.endAppearanceTransition()
}
it("deletes the selected row and reloads the tableView's data") {
let tableView = viewController.tableView
let indexPath = NSIndexPath(forRow: 0, inSection: 0)
viewController.tableView(tableView, didSelectRowAtIndexPath: indexPath)
var cell = viewController.tableView(tableView, cellForRowAtIndexPath: indexPath)
expect(cell.textLabel?.text).to(beNil())
}
}
}
}
// Objective-C
#import <UIKit/UIKit.h>
#import <Quick/Quick.h>
#import <Nimble/Nimble.h>
QuickSpecBegin(DolphinTableViewControllerSpec)
describe(@"viewDidLoad", ^{
__block DolphinTableViewController *viewController = nil;
beforeEach(^{
viewController = [[DolphinTableViewController alloc] init];
});
it(@"loads the table view with three types of dolphin", ^{
beforeEach(^{
// Accessing the view property causes the UIKit framework to trigger the necessary methods to render the view.
[viewController view];
});
UITableView *tableView = [viewController tableView];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
UITableViewCell *cell = [viewController tableView:tableView cellForRowAtIndexPath:indexPath];
expect(@([[cell textLabel] text])).to(equal(@"Bottlenose"));
});
}
describe(@"didSelectRowAtIndexPath", ^{
__block DolphinTableViewController *viewController = nil;
beforeEach(^{
// Causes the UIKit framework to trigger the necessary methods to render the view and perform viewWillAppear: and
viewController = [[DolphinTableViewController alloc] init];
[viewController beginAppearanceTransition:YES animated:NO];
[viewController endAppearanceTransition];
});
it(@"deletes the selected row and reloads the tableView's data", ^{
UITableView *tableView = [viewController tableView];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[viewController tableView:tableView didSelectRowAtIndexPath:indexPath];
UITableViewCell *cell = [viewController tableView:tableView cellForRowAtIndexPath:indexPath];
expect(@([[cell textLabel] text])).to(beNil());
});
}
QuickSpecEnd
This module is beta software, it currently supports Xcode 6 Beta 4.
Quick provides the syntax to define examples and example groups. Nimble
provides the expect(...).to
assertion syntax. You may use 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 Quick and Nimble repositories
- Add
Quick.xcodeproj
andNimble.xcodeproj
to your test target - Link
Quick.framework
andNimble.framework
- Start writing specs!
Example projects with this complete setup is available in the
Examples
directory.
git clone [email protected]:Quick/Quick.git Vendor/Quick
git clone [email protected]:Quick/Nimble.git Vendor/Nimble
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 best way to include Quick in a Git repository is by using Git submodules. Git submodules are great because:
- They track exactly which version of Quick is being used
- It's easy to update Quick to the latest--or any other--version
To use Git submodules, follow the same steps as above, except instead of cloning the Quick and Nimble repositories, add them to your project as submodules:
mkdir Vendor # you can keep your submodules in their own directory
git submodule add [email protected]:Quick/Quick.git Vendor/Quick
git submodule add [email protected]:Quick/Nimble.git Vendor/Nimble
git submodule update --init --recursive
If you ever want to update the Quick submodule to latest version, enter the Quick directory and pull from the master repository:
cd Vendor/Quick
git pull --rebase origin master
Your Git repository will track changes to submodules. You'll want to commit the fact that you've updated the Quick submodule:
git commit -m "Updated Quick submodule"
After other people clone your repository, they'll have to pull down the
submodules as well. They can do so by running the git submodule update
command:
git submodule update --init --recursive
You can read more about Git submodules here. To see examples of Git submodules in action, check out any of the repositories linked to in the "Who Uses Quick" section of this guide.
If you would like to use Quick with CocoaPods today, you need to install the
beta build of CocoaPods via [sudo] gem install cocoapods --pre
then add Quick
to your Podfile.
pod 'Quick', :git => 'https://github.com/Quick/Quick', :tag => 'v0.2.2'
How to Install Quick using Carthage
As Test targets do not have the "Embedded Binaries" section, the frameworks must be added to the target's "Link Binary With Libraries" as well as a "Copy Files" build phase to copy them to the target's Frameworks destination.
As Carthage builds dynamic frameworks, you will need a valid code signing identity set up.
-
Add Quick to your Cartfile.private
github "Quick/Quick"
-
Run
carthage update
-
From your
Carthage/Build/[platform]/
directory, add both Quick and Nimble to your test target's Link Binary With Libraries build phase -
For your test target, create a new build phase of type Copy Files
This is not 'the one and only way' to use Carthage to manage dependencies, for further reference check out the Carthage documentation
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]:Quick/Quick.git
$ rake templates:install
Uninstalling is easy, too:
$ rake templates:uninstall
The Quick configuration object exposes custom options to alter the behavior of
the framework. The intention of this hook is to provide a global configuration
to meet the needs of your project. QuickConfiguration is never meant to be
instantiated. If you do so, an exception will be raised. Instead, subclass it
and override the configure()
class function, like so:
// Swift
import Quick
import Nimble
class ProjectDataTestConfiguration: QuickConfiguration {
override class func configure(configuration : Configuration) {
// set options on the configuration object
}
}
// Objective-C
#import <Quick/Quick.h>
#import <Nimble/Nimble.h>
QuickConfigurationBegin(ProjectDataTestConfiguration)
+ (void)configure:(Configuration *configuration) {
// set options of the configuration object
}
QuickConfigurationEnd
Projects may include several configurations. However, Quick does not make any guarantee about the order in which they are executed.
Similar to beforeEach()
and afterEach()
functions in QuickSpec(s), global
filters can be applied to all tests. For example, a computed value may need to
be reset before each test case. In the following case, it is assumed that the
tests rely on the height
property of the Dorsal
singleton to be 0 in order
to do some sort of mathematical computation.
// Swift
import Quick
import Nimble
class FinConfiguration: QuickConfiguration {
override class func configure(configuration: Configuration) {
configuration.beforeEach() {
fin = Dorsal.sharedFin()
fin.height = 0
}
}
}
// Objective-C
#import <Quick/Quick.h>
#import "Dorsal.h"
QuickConfigurationBegin(FinConfiguration)
+ (void)configure:(Configuration *)configuration {
[configuration beforeEach:^{
Dorsal *fin = [Dorsal sharedFin];
fin.height = 0;
}];
}
QuickConfigurationEnd
Every test resets the height
property to 0. Therefore, our tests with various
computations remain entirely independent.
In addition, Quick allows you to access metadata regarding the tests. The
framework provides beforeSuite()
and afterSuite()
closures with
metadata. Metadata is passed into the closure like so:
// Swift
import Quick
import Nimble
class Fin2Configuration: QuickConfiguration {
override class func configure(configuration: Configuration) {
configuration.beforeEach({ (exampleMetadata : ExampleMetadata) -> () in
// work with metadata
})
}
}
// Objective-C
#import <Quick/Quick.h>
#import "Dorsal.h"
QuickConfigurationBegin(Fin2Configuration)
+ (void)configure:(Configuration *)configuration {
[configuration beforeEachWithMetadata:^(ExampleMetadata *data) {
// work with metadata
}];
}
QuickConfigurationEnd
Quick is used by many companies, open-source projects, and individuals, including GitHub and ReactiveCocoa. See examples below:
- https://github.com/ReactiveCocoa/ReactiveCocoa
- https://github.com/github/Archimedes
- https://github.com/libgit2/objective-git
- https://github.com/jspahrsummers/RXSwift
- https://github.com/artsy/eidolon
- https://github.com/AshFurrow/Moya
- https://github.com/nerdyc/Squeal
- https://github.com/pepibumur/SugarRecord
Add an issue or tweet if you'd like to be added to this list.
Apache 2.0 license. See the LICENSE
file for details.