Back to Quick

Organized Tests with Quick Examples and Example Groups

Documentation/en-us/QuickExamplesAndGroups.md

7.6.214.7 KB
Original Source

Organized Tests with Quick Examples and Example Groups

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:

  1. They encourage you to write descriptive test names.
  2. They greatly simplify the test code in the "arrange" step of your tests.

Examples Using it

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
// Swift

import Quick
import Nimble
import Sea

class DolphinSpec: QuickSpec {
  override class func spec() {
    it("is friendly") {
      expect(Dolphin().isFriendly).to(beTruthy())
    }

    it("is smart") {
      expect(Dolphin().isSmart).to(beTruthy())
    }
  }
}
objc
// 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! :v: :sunglasses:

Example Groups Using describe and context

Example groups are logical groupings of examples. Example groups can share setup and teardown code.

Describing Classes and Methods Using describe

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
// Swift

import Quick
import Nimble

class DolphinSpec: QuickSpec {
  override class 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())
        }
      }
    }
  }
}
objc
// 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:

  1. DolphinSpec.a_dolphin_its_click_is_loud
  2. DolphinSpec.a_dolphin_its_click_has_a_high_frequency

Again, it's clear what each of these examples is testing.

Sharing Setup/Teardown Code Using beforeEach and afterEach

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
// Swift

import Quick
import Nimble

class DolphinSpec: QuickSpec {
  override class 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())
        }
      }
    }
  }
}
objc
// 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.

Sharing Setup/Teardown Code Using justBeforeEach

In some cases you will want to have a group of tests with a common test setup but different configuration of that setup code in the individual tests. This makes the most sense when you have an API that you are mocking, and logic on top of that API that will make different decisions based on the result.

swift
// Swift

describe("a sleeping dolphin") {
    var sleeping: Bool!
    var click: Click!

    justBeforeEach {
        dolphin = Dolphin(sleeping: sleeping)
        click = dolphin.click()
    }

    context("not sleeping") {
        beforeEach {
            sleeping = false
        }

        it("then it makes clicks that are loud") {
            expect(click.isLoud).to(beTrue())
        }
    }

    context("sleeping") {
        beforeEach {
            sleeping = true
        }

        it("then it does not make clicks that are loud") {
            expect(click.isLoud).to(beFalse())
        }
    }
}

In this example, a sleeping dolphin will not make Clicks. We wish to build our tests so that we only construct one Dolphin object, and only invoke the .click() method once. This requires the use of justBeforeEach, which invokes our constructor and method after the two beforeEach blocks have been invoked. Without justBeforeEach, we would have to trigger the API call twice in our tests, with a different argument each time. For tests that have a lot of boilerplate or setup required, this can significantly reduce lines of code and complexity.

Throwing from a beforeEach, or justBeforeEach, or afterEach.

Sometimes, in your setup and teardown code, you want to cancel executing the remainder of the test if the setup/teardown runs into an error. You can do that in Swift by throwing any error. This will fail the test, and prevent any setup/teardown nested in the test tree from running. Similar to if you throw an error during an XCTestCase's setupWithError() method.

Because of Quick's tree-structure for Tests, this prevents setup/teardown defined in nested example groups from running. Setup and teardown blocks from parent example groups will be run.

For example, with the following test:

swift
// Swift

import Quick
import Nimble

class DolphinSpec: QuickSpec {
  override class func spec() {
    describe("a dolphin") {
      var dolphin: Dolphin!
      beforeEach {
        dolphin = Dolphin()
        throw DolphinError()
      }

      afterEach {
        dolphin.rest()
      }

      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())
        }
      }
    }
  }
}

None of the setup or tests in the "its click" example group are run. Once the DolphinError() is thrown, the test it's running in will be marked as failing. Additionally, the dolphin.rest() function in the afterEach is still called.

It's important to note that, when a beforeEach throws, none of the justBeforeEach blocks in the test will run.

Specifying Conditional Behavior Using context

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
// Swift

import Quick
import Nimble

class DolphinSpec: QuickSpec {
  override class 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))
          }
        }
      }
    }
  }
}
objc
// 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.

Test Readability: Quick and XCTest

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:

swift
func testDolphin_click_whenTheDolphinIsNearSomethingInteresting_isEmittedThreeTimes() {
  // ...
}

Using Quick, the conditions are much easier to read, and we can perform setup for each example group:

swift
describe("a dolphin") {
  describe("its click") {
    context("when the dolphin is near something interesting") {
      it("is emitted three times") {
        // ...
      }
    }
  }
}

Temporarily Disabling Examples or Groups

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
// 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.
}
objc
// 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.
});

Temporarily Running a Subset of Focused Examples

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:

swift
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.
}
objc
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.
});

Global Setup/Teardown Using beforeSuite and afterSuite

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
// Swift

import Quick

class DolphinSpec: QuickSpec {
  override class func spec() {
    beforeSuite {
      OceanDatabase.createDatabase(name: "test.db")
      OceanDatabase.connectToDatabase(name: "test.db")
    }

    afterSuite {
      OceanDatabase.teardownDatabase(name: "test.db")
    }

    describe("a dolphin") {
      // ...
    }
  }
}
objc
// 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.

Accessing Metadata for the Current Example

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.

swift
beforeEach { exampleMetadata in
  println("Example number \(exampleMetadata.exampleIndex) is about to be run.")
}

afterEach { exampleMetadata in
  println("Example number \(exampleMetadata.exampleIndex) has run.")
}
objc
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);
});