Quick uses a special syntax to define examples and example groups.
In Effective Tests Using XCTest: Arrange, Act, and Assert, we learned that a good test method name is crucial--when a test starts failing, it's the best way to determine whether we have to fix the application code or update the test.
Quick examples and example groups serve two purposes:
- They encourage you to write descriptive test names.
- They greatly simplify the test code in the "arrange" step of your tests.
Examples, defined with the it
function, use assertions to demonstrate
how code should behave. These are like test methods in XCTest.
it
takes two parameters: the name of the example, and a closure.
The examples below specify how the Sea.Dolphin
class should behave.
A new dolphin should be smart and friendly:
// Swift
import Quick
import Nimble
import Sea
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;
@import Nimble;
QuickSpecBegin(DolphinSpec)
it(@"is friendly", ^{
expect(@([[Dolphin new] isFriendly])).to(beTruthy());
});
it(@"is smart", ^{
expect(@([[Dolphin new] isSmart])).to(beTruthy());
});
QuickSpecEnd
Use descriptions to make it clear what your examples are testing. Descriptions can be of any length and 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;
@import Nimble;
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
When these two examples are run in Xcode, they'll display the
description from the describe
and it
functions:
DolphinSpec.a_dolphin_its_click_is_loud
DolphinSpec.a_dolphin_its_click_has_a_high_frequency
Again, it's clear what each of these examples is testing.
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;
@import Nimble;
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;
@import Nimble;
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
Strictly speaking, the context
keyword is a synonym for describe
,
but thoughtful use will make your spec easier to understand.
In Effective Tests Using XCTest: Arrange, Act, and Assert, we looked at how one test per condition was a great way to organize test code. In XCTest, that leads to long test method names:
func testDolphin_click_whenTheDolphinIsNearSomethingInteresting_isEmittedThreeTimes() {
// ...
}
Using Quick, the conditions are much easier to read, and we can perform setup for each example group:
describe("a dolphin") {
describe("its click") {
context("when the dolphin is near something interesting") {
it("is emitted three times") {
// ...
}
}
}
}
You can temporarily disable examples or example groups that don't pass yet. The names of the examples will be printed out along with the test results, but they won't be run.
You can disable an example or group by prepending x
:
// 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 examples 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("is loud") {
// ...only this focused example will be run.
}
it("has a high frequency") {
// ...this example is not focused, and will not be run.
}
fcontext("when the dolphin is near something interesting") {
// ...examples in this group are also focused, so they'll be run.
}
fit(@"is loud", {
// ...only this focused example will be run.
});
it(@"has a high frequency", ^{
// ...this example is not focused, and will not be run.
});
fcontext(@"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;
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
and afterSuite
as you like. All
beforeSuite
closures will be executed before any tests run, and all
afterSuite
closures will be executed after all the tests are finished.
There is no guarantee as to what order these closures will be executed in.
There may be some cases in which you'd like the know the name of the example
that is currently being run, or how many have been run so far. Quick provides
access to this metadata in beforeEach
and afterEach
closures.
beforeEach { exampleMetadata in
println("Example number \(exampleMetadata.exampleIndex) is about to be run.")
}
afterEach { exampleMetadata in
println("Example number \(exampleMetadata.exampleIndex) has run.")
}
beforeEachWithMetadata(^(ExampleMetadata *exampleMetadata){
NSLog(@"Example number %l is about to be run.", (long)exampleMetadata.exampleIndex);
});
afterEachWithMetadata(^(ExampleMetadata *exampleMetadata){
NSLog(@"Example number %l has run.", (long)exampleMetadata.exampleIndex);
});