├── .gitignore ├── .gitmodules ├── .travis.yml ├── CONTRIBUTING.md ├── Documentation ├── ArrangeActAssert.md ├── BehavioralTesting.md ├── ConfiguringQuick.md ├── InstallingFileTemplates.md ├── InstallingQuick.md ├── MoreResources.md ├── NimbleAssertions.md ├── QuickExamplesAndGroups.md ├── QuickInObjectiveC.md ├── README.md ├── SettingUpYourXcodeProject.md ├── SharedExamples.md └── TestingApps.md ├── LICENSE ├── Quick Templates ├── Quick Configuration Class.xctemplate │ ├── Objective-C │ │ ├── ___FILEBASENAME___.h │ │ └── ___FILEBASENAME___.m │ ├── Swift │ │ └── ___FILEBASENAME___.swift │ ├── TemplateIcon.icns │ └── TemplateInfo.plist └── Quick Spec Class.xctemplate │ ├── Objective-C │ └── ___FILEBASENAME___.m │ ├── Swift │ └── ___FILEBASENAME___.swift │ ├── TemplateIcon.icns │ └── TemplateInfo.plist ├── Quick.podspec ├── Quick.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── Quick-OSX.xcscheme │ └── Quick-iOS.xcscheme ├── Quick.xcworkspace └── contents.xcworkspacedata ├── Quick ├── Callsite.swift ├── Configuration │ ├── Configuration.swift │ ├── QuickConfiguration.h │ └── QuickConfiguration.m ├── DSL │ ├── DSL.swift │ ├── QCKDSL.h │ ├── QCKDSL.m │ ├── World+DSL.h │ └── World+DSL.swift ├── Example.swift ├── ExampleGroup.swift ├── ExampleMetadata.swift ├── Filter.swift ├── Hooks │ ├── Closures.swift │ ├── ExampleHooks.swift │ └── SuiteHooks.swift ├── Info.plist ├── NSString+QCKSelectorName.h ├── NSString+QCKSelectorName.m ├── Quick.h ├── QuickSpec.h ├── QuickSpec.m ├── World.h └── World.swift ├── QuickFocusedTests ├── FocusedTests+ObjC.m ├── FocusedTests.swift └── Info.plist ├── QuickTests ├── Fixtures │ └── FunctionalTests_SharedExamplesTests_SharedExamples.swift ├── FunctionalTests │ ├── AfterEachTests+ObjC.m │ ├── AfterEachTests.swift │ ├── AfterSuiteTests+ObjC.m │ ├── AfterSuiteTests.swift │ ├── BeforeEachTests+ObjC.m │ ├── BeforeEachTests.swift │ ├── BeforeSuiteTests+ObjC.m │ ├── BeforeSuiteTests.swift │ ├── Configuration │ │ ├── AfterEach │ │ │ ├── Configuration+AfterEach.swift │ │ │ └── Configuration+AfterEachTests.swift │ │ └── BeforeEach │ │ │ ├── Configuration+BeforeEach.swift │ │ │ └── Configuration+BeforeEachTests.swift │ ├── FailureTests+ObjC.m │ ├── FailureUsingXCTAssertTests+ObjC.m │ ├── ItTests+ObjC.m │ ├── ItTests.swift │ ├── PendingTests+ObjC.m │ ├── PendingTests.swift │ ├── SharedExamples+BeforeEachTests+ObjC.m │ ├── SharedExamples+BeforeEachTests.swift │ ├── SharedExamplesTests+ObjC.m │ └── SharedExamplesTests.swift ├── Helpers │ ├── QCKSpecRunner.h │ ├── QCKSpecRunner.m │ ├── QuickTestsBridgingHeader.h │ └── XCTestObservationCenter+QCKSuspendObservation.h ├── Info.plist └── QuickConfigurationTests.m ├── README.md ├── Rakefile └── circle.yml /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | *.xcscmblueprint 20 | 21 | # CocoaPods 22 | # 23 | # We recommend against adding the Pods directory to your .gitignore. However 24 | # you should judge for yourself, the pros and cons are mentioned at: 25 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 26 | # 27 | # Pods/ 28 | 29 | # Mac OS X 30 | .DS_Store 31 | 32 | # Quick 33 | Quick.framework.zip 34 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Externals/Nimble"] 2 | path = Externals/Nimble 3 | url = https://github.com/Quick/Nimble.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | osx_image: xcode7 2 | language: objective-c 3 | 4 | before_install: git submodule update --init --recursive 5 | script: "rake" 6 | 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](http://doctoc.herokuapp.com/)* 4 | 5 | - [Welcome to Quick!](#welcome-to-quick!) 6 | - [Reporting Bugs](#reporting-bugs) 7 | - [Building the Project](#building-the-project) 8 | - [Pull Requests](#pull-requests) 9 | - [Style Conventions](#style-conventions) 10 | - [Core Members](#core-members) 11 | - [Code of Conduct](#code-of-conduct) 12 | - [Creating a Release](#creating-a-release) 13 | 14 | 15 | 16 | # Welcome to Quick! 17 | 18 | We're building a testing framework for a new generation of Swift and 19 | Objective-C developers. 20 | 21 | Quick should be easy to use and easy to maintain. Let's keep things 22 | simple and well-tested. 23 | 24 | ## Reporting Bugs 25 | 26 | Nothing is off-limits. If you're having a problem, we want to hear about 27 | it. 28 | 29 | - See a crash? File an issue. 30 | - Code isn't compiling, but you don't know why? Sounds like you should 31 | submit a new issue, bud. 32 | - Went to the kitchen, only to forget why you went in the first place? 33 | Better submit an issue. 34 | 35 | ## Building the Project 36 | 37 | - After cloning the repository, run `git submodule update --init` to pull the Nimble submodule. 38 | - Use `Quick.xcworkspace` to work on Quick. The workspace includes 39 | Nimble, which is used in Quick's tests. 40 | 41 | ## Pull Requests 42 | 43 | - Nothing is trivial. Submit pull requests for anything: typos, 44 | whitespace, you name it. 45 | - Not all pull requests will be merged, but all will be acknowledged. If 46 | no one has provided feedback on your request, ping one of the owners 47 | by name. 48 | - Make sure your pull request includes any necessary updates to the 49 | README or other documentation. 50 | - Be sure the unit tests for both the OS X and iOS targets of both Quick 51 | and Nimble pass before submitting your pull request. You can run all 52 | the iOS and OS X unit tests using `rake`. 53 | - To make minor updates to old versions of Quick that support Swift 54 | 1.1, issue a pull request against the `swift-1.1` branch. The master 55 | branch supports Swift 1.2. Travis CI will only pass for pull requests 56 | issued against the `swift-1.1` branch. 57 | 58 | ### Style Conventions 59 | 60 | - Indent using 4 spaces. 61 | - Keep lines 100 characters or shorter. Break long statements into 62 | shorter ones over multiple lines. 63 | - In Objective-C, use `#pragma mark -` to mark public, internal, 64 | protocol, and superclass methods. See `QuickSpec.m` for an example. 65 | 66 | ## Core Members 67 | 68 | If a few of your pull requests have been merged, and you'd like a 69 | controlling stake in the project, file an issue asking for write access 70 | to the repository. 71 | 72 | ### Code of Conduct 73 | 74 | Your conduct as a core member is your own responsibility, but here are 75 | some "ground rules": 76 | 77 | - Feel free to push whatever you want to master, and (if you have 78 | ownership permissions) to create any repositories you'd like. 79 | 80 | Ideally, however, all changes should be submitted as GitHub pull 81 | requests. No one should merge their own pull request, unless no 82 | other core members respond for at least a few days. 83 | 84 | Pull requests should be issued from personal forks. The Quick repo 85 | should be reserved for long-running feature branches. 86 | 87 | If you'd like to create a new repository, it'd be nice if you created 88 | a GitHub issue and gathered some feedback first. 89 | 90 | - It'd be awesome if you could review, provide feedback on, and close 91 | issues or pull requests submitted to the project. Please provide kind, 92 | constructive feedback. Please don't be sarcastic or snarky. 93 | 94 | ## Creating a Release 95 | 96 | The process is relatively straight forward, but here's is a useful checklist for tagging: 97 | 98 | - Bump the version in `Quick.podspec` (update, commit, push to github) 99 | - Look a changes from the previously tagged release and write release notes: `git log v0.4.0...HEAD` 100 | - The release notes should include user-facing information (api breakages, new features, bug fixes) 101 | - Tag the version: `git tag -s vA.B.C -F release-notes-file` 102 | - Push the tag: `git push origin vA.B.C` 103 | - Push the podspec file to trunk: `pod trunk push Quick.podspec` 104 | - Build the carthage pre-built binary: 105 | - `carthage build --no-skip-current` 106 | - `carthage archive Quick` 107 | - Go to [github releases](https://github.com/Quick/Quick/releases) and mark the tagged commit as a release. 108 | - Use the same release notes you created for the tag, but tweak up formatting for github. 109 | - Attach the carthage release `Quick.framework.zip` to the release. 110 | - Announce! 111 | -------------------------------------------------------------------------------- /Documentation/ArrangeActAssert.md: -------------------------------------------------------------------------------- 1 | # Effective Tests Using XCTest: Arrange, Act, and Assert 2 | 3 | Whether you're using XCTest, Quick, or another testing framework, you can write 4 | effective unit tests by following a simple pattern: 5 | 6 | 1. Arrange 7 | 2. Act 8 | 3. Assert 9 | 10 | ## Using Arrange, Act, and Assert 11 | 12 | For example, let's look at a simple class called `Banana`: 13 | 14 | ```swift 15 | // Banana/Banana.swift 16 | 17 | /** A delicious banana. Tastes better if you peel it first. */ 18 | public class Banana { 19 | private var isPeeled = false 20 | 21 | /** Peels the banana. */ 22 | public func peel() { 23 | isPeeled = true 24 | } 25 | 26 | /** You shouldn't eat a banana unless it's been peeled. */ 27 | public var isEdible: Bool { 28 | return isPeeled 29 | } 30 | } 31 | ``` 32 | 33 | Let's verify the `Banana.peel()` method does what it's supposed to: 34 | 35 | ```swift 36 | // BananaTests/BananaTests.swift 37 | 38 | class BananaTests: XCTestCase { 39 | func testPeel() { 40 | // Arrange: Create the banana we'll be peeling. 41 | let banana = Banana() 42 | 43 | // Act: Peel the banana. 44 | banana.peel() 45 | 46 | // Assert: Verify that the banana is now edible. 47 | XCTAssertTrue(banana.isEdible) 48 | } 49 | } 50 | ``` 51 | 52 | ## Using Clear Test Names 53 | 54 | Our `testPeel()` makes sure that, if the `Banana.peel()` method ever 55 | stops working right, we'll know. This usually happens when our application 56 | code changes, which either means: 57 | 58 | 1. We accidentally broke our application code, so we have to fix the application code 59 | 2. We changed how our application code works--maybe because we're adding a new 60 | feature--so we have to change the test code 61 | 62 | If our tests start breaking, how do we know which one of these cases applies? It might 63 | surprise you that **the name of the test** is our best indication. Good test names: 64 | 65 | 1. Are clear about what is being tested. 66 | 2. Are clear about when the test should pass or fail. 67 | 68 | Is our `testPeel()` method clearly named? Let's make it clearer: 69 | 70 | ```diff 71 | // BananaTests.swift 72 | 73 | -func testPeel() { 74 | +func testPeel_makesTheBananaEdible() { 75 | // Arrange: Create the banana we'll be peeling. 76 | let banana = Banana() 77 | 78 | // Act: Peel the banana. 79 | banana.peel() 80 | 81 | // Assert: Verify that the banana is now edible. 82 | XCTAssertTrue(banana.isEdible) 83 | } 84 | ``` 85 | 86 | The new name: 87 | 88 | 1. Is clear about what is being tested: `testPeel` indicates it's the `Banana.peel()` method. 89 | 2. Is clear about when the test should pass: `makesTheBananaEdible` indicates the 90 | banana is edible once the method has been called. 91 | 92 | ## Testing Conditions 93 | 94 | Let's say we want to offer people bananas, using a function called `offer()`: 95 | 96 | ```swift 97 | // Banana/Offer.swift 98 | 99 | /** Given a banana, returns a string that can be used to offer someone the banana. */ 100 | public func offer(banana: Banana) -> String { 101 | if banana.isEdible { 102 | return "Hey, want a banana?" 103 | } else { 104 | return "Hey, want me to peel this banana for you?" 105 | } 106 | } 107 | ``` 108 | 109 | Our application code does one of two things: 110 | 111 | 1. Either it offers a banana that's already been peeled... 112 | 2. ...or it offers an unpeeled banana. 113 | 114 | Let's write tests for these two cases: 115 | 116 | ```swift 117 | // BananaTests/OfferTests.swift 118 | 119 | class OfferTests: XCTestCase { 120 | func testOffer_whenTheBananaIsPeeled_offersTheBanana() { 121 | // Arrange: Create a banana and peel it. 122 | let banana = Banana() 123 | banana.peel() 124 | 125 | // Act: Create the string used to offer the banana. 126 | let message = offer(banana) 127 | 128 | // Assert: Verify it's the right string. 129 | XCTAssertEqual(message, "Hey, want a banana?") 130 | } 131 | 132 | func testOffer_whenTheBananaIsntPeeled_offersToPeelTheBanana() { 133 | // Arrange: Create a banana. 134 | let banana = Banana() 135 | 136 | // Act: Create the string used to offer the banana. 137 | let message = offer(banana) 138 | 139 | // Assert: Verify it's the right string. 140 | XCTAssertEqual(message, "Hey, want me to peel this banana for you?") 141 | } 142 | } 143 | ``` 144 | 145 | Our test names clearly indicate the **conditions** under which our tests should pass: 146 | in the case that `whenTheBananaIsntPeeled`, `offer()` should `offersTheBanana`. And if 147 | the banana isn't peeled? Well, we have a test for that, too! 148 | 149 | Notice that we have one test per `if` statement in our application code. 150 | This is a great pattern when writing tests: it makes sure every set of conditions 151 | is tested. If one of those conditions no longer works, or needs to be changed, we'll know 152 | exactly which test needs to be looked at. 153 | 154 | ## Shorter "Arrange" Steps with `XCTestCase.setUp()` 155 | 156 | Both of our `OfferTests` tests contain the same "Arrange" code: they both 157 | create a banana. We should move that code into a single place. Why? 158 | 159 | 1. As-is, if we change the `Banana` initializer, we'll have to change every test that creates a banana. 160 | 2. Our test methods will be shorter--which is a good thing if (and **only if**) that makes 161 | the tests easier to read. 162 | 163 | Let's move the `Banana` initialization into the `XCTestCase.setUp()` method, which is called 164 | once before every test method. 165 | 166 | ```diff 167 | // OfferTests.swift 168 | 169 | class OfferTests: XCTestCase { 170 | + var banana: Banana! 171 | + 172 | + override func setUp() { 173 | + super.setUp() 174 | + banana = Banana() 175 | + } 176 | + 177 | func testOffer_whenTheBananaIsPeeled_offersTheBanana() { 178 | - // Arrange: Create a banana and peel it. 179 | - let banana = Banana() 180 | + // Arrange: Peel the banana. 181 | banana.peel() 182 | 183 | // Act: Create the string used to offer the banana. 184 | let message = offer(banana) 185 | 186 | // Assert: Verify it's the right string. 187 | XCTAssertEqual(message, "Hey, want a banana?") 188 | } 189 | 190 | func testOffer_whenTheBananaIsntPeeled_offersToPeelTheBanana() { 191 | - // Arrange: Create a banana. 192 | - let banana = Banana() 193 | - 194 | // Act: Create the string used to offer the banana. 195 | let message = offer(banana) 196 | 197 | // Assert: Verify it's the right string. 198 | XCTAssertEqual(message, "Hey, want me to peel this banana for you?") 199 | } 200 | } 201 | ``` 202 | 203 | ## Sharing "Arrange" Code Across Multiple Tests 204 | 205 | If you find yourself using the same "arrange" steps across multiple tests, 206 | you may want to define a helper function within your test target: 207 | 208 | ```swift 209 | // BananaTests/BananaHelpers.swift 210 | 211 | internal func createNewPeeledBanana() -> Banana { 212 | let banana = Banana() 213 | banana.peel() 214 | return banana 215 | } 216 | ``` 217 | 218 | > Use a function to define your helpers: functions can't be subclassed, nor 219 | can they retain any state. Subclassing and mutable state can make your tests 220 | harder to read. 221 | -------------------------------------------------------------------------------- /Documentation/BehavioralTesting.md: -------------------------------------------------------------------------------- 1 | # Don't Test Code, Instead Verify Behavior 2 | 3 | Tests should only fail if the application **behaves differently**. 4 | They should test *what* the application code does, not *how* it does those things. 5 | 6 | - Tests that verify *what* an application does are **behavioral tests**. 7 | - Tests that break if the application code changes, even if the behavior 8 | remains the same, are **brittle tests**. 9 | 10 | Let's say we have a banana database, called `GorillaDB`. 11 | `GorillaDB` is a key-value store for bananas. We can save bananas: 12 | 13 | ```swift 14 | let database = GorillaDB() 15 | let banana = Banana() 16 | database.save(banana: banana, key: "my-banana") 17 | ``` 18 | 19 | And we can restore bananas from disk later: 20 | 21 | ```swift 22 | let banana = database.load(key: "my-banana") 23 | ``` 24 | 25 | ## Brittle Tests 26 | 27 | How can we test this behavior? One way would be to check the size of the database 28 | after we save a banana: 29 | 30 | ```swift 31 | // GorillaDBTests.swift 32 | 33 | func testSave_savesTheBananaToTheDatabase() { 34 | // Arrange: Create a database and get its original size. 35 | let database = GorillaDB() 36 | let originalSize = database.size 37 | 38 | // Act: Save a banana to the database. 39 | let banana = Banana() 40 | database.save(banana: banana, key: "test-banana") 41 | 42 | // Assert: The size of the database should have increased by one. 43 | XCTAssertEqual(database.size, originalSize + 1) 44 | } 45 | ``` 46 | 47 | 48 | Imagine, however, that the source code of `GorillaDB` changes. In order to make 49 | reading bananas from the database faster, it maintains a cache of the most frequently 50 | used bananas. `GorillaDB.size` grows as the size of the cache grows, and our test fails: 51 | 52 | ![](http://cl.ly/image/0G2s3B3d2F3O/Screen%20Shot%202015-02-23%20at%204.07.32%20PM.png) 53 | 54 | ## Behavioral Tests 55 | 56 | The key to writing behavioral tests is determining exactly what you're expecting 57 | your application code to do. 58 | 59 | In the context of our `testSave_savesTheBananaToTheDatabase` test: what is the 60 | behavior we expect when we "save" a banana to the database? "Saving" implies, to me, 61 | that we can load it later. So instead of testing that the size of the database increases, 62 | we should test that we can load a banana. 63 | 64 | ```diff 65 | // GorillaDBTests.swift 66 | 67 | func testSave_savesTheBananaToTheDatabase() { 68 | // Arrange: Create a database and get its original size. 69 | let database = GorillaDB() 70 | - let originalSize = database.size 71 | 72 | // Act: Save a banana to the database. 73 | let banana = Banana() 74 | database.save(banana: banana, key: "test-banana") 75 | 76 | - // Assert: The size of the database should have increased by one. 77 | - XCTAssertEqual(database.size, originalSize + 1) 78 | + // Assert: The bananas saved to and loaded from the database should be the same. 79 | + XCTAssertEqual(database.load(key: "test-banana"), banana) 80 | } 81 | ``` 82 | 83 | The key to writing behavioral tests is asking: 84 | 85 | - What exactly should this application code do? 86 | - Is my test verifying *only* that behavior? 87 | Or could it fail due to other aspects of how the code works? 88 | -------------------------------------------------------------------------------- /Documentation/ConfiguringQuick.md: -------------------------------------------------------------------------------- 1 | # Configuring How Quick Behaves 2 | 3 | You can customize how Quick behaves by subclassing `QuickConfiguration` and 4 | overriding the `QuickConfiguration.Type.configure()` class method: 5 | 6 | ```swift 7 | // Swift 8 | 9 | import Quick 10 | 11 | class ProjectDataTestConfiguration: QuickConfiguration { 12 | override class func configure(configuration: Configuration) { 13 | // ...set options on the configuration object here. 14 | } 15 | } 16 | ``` 17 | 18 | ```objc 19 | // Objective-C 20 | 21 | #import 22 | 23 | QuickConfigurationBegin(ProjectDataTestConfiguration) 24 | 25 | + (void)configure:(Configuration *configuration) { 26 | // ...set options on the configuration object here. 27 | } 28 | 29 | QuickConfigurationEnd 30 | ``` 31 | 32 | Projects may include several configurations. Quick does not make any 33 | guarantee about the order in which those configurations are executed. 34 | 35 | ## Adding Global `beforeEach` and `afterEach` Closures 36 | 37 | Using `QuickConfiguration.beforeEach` and `QuickConfiguration.afterEach`, you 38 | can specify closures to be run before or after *every* example in a test suite: 39 | 40 | ```swift 41 | // Swift 42 | 43 | import Quick 44 | import Sea 45 | 46 | class FinConfiguration: QuickConfiguration { 47 | override class func configure(configuration: Configuration) { 48 | configuration.beforeEach { 49 | Dorsal.sharedFin().height = 0 50 | } 51 | } 52 | } 53 | ``` 54 | 55 | ```objc 56 | // Objective-C 57 | 58 | #import 59 | #import "Dorsal.h" 60 | 61 | QuickConfigurationBegin(FinConfiguration) 62 | 63 | + (void)configure:(Configuration *)configuration { 64 | [configuration beforeEach:^{ 65 | [Dorsal sharedFin].height = 0; 66 | }]; 67 | } 68 | 69 | QuickConfigurationEnd 70 | ``` 71 | 72 | In addition, Quick allows you to access metadata regarding the current 73 | example being run: 74 | 75 | ```swift 76 | // Swift 77 | 78 | import Quick 79 | 80 | class SeaConfiguration: QuickConfiguration { 81 | override class func configure(configuration: Configuration) { 82 | configuration.beforeEach { exampleMetadata in 83 | // ...use the example metadata object to access the current example name, and more. 84 | } 85 | } 86 | } 87 | ``` 88 | 89 | ```objc 90 | // Objective-C 91 | 92 | #import 93 | 94 | QuickConfigurationBegin(SeaConfiguration) 95 | 96 | + (void)configure:(Configuration *)configuration { 97 | [configuration beforeEachWithMetadata:^(ExampleMetadata *data) { 98 | // ...use the example metadata object to access the current example name, and more. 99 | }]; 100 | } 101 | 102 | QuickConfigurationEnd 103 | ``` 104 | -------------------------------------------------------------------------------- /Documentation/InstallingFileTemplates.md: -------------------------------------------------------------------------------- 1 | # Installing Quick File Templates 2 | 3 | The Quick repository includes file templates for both Swift and 4 | Objective-C specs. 5 | 6 | ## Alcatraz 7 | 8 | Quick templates can be installed via [Alcatraz](https://github.com/supermarin/Alcatraz), 9 | a package manager for Xcode. Just search for the templates from the 10 | Package Manager window. 11 | 12 | ![](http://f.cl.ly/items/3T3q0G1j0b2t1V0M0T04/Screen%20Shot%202014-06-27%20at%202.01.10%20PM.png) 13 | 14 | ## Manually via the Rakefile 15 | 16 | To manually install the templates, just clone the repository and 17 | run the `templates:install` rake task: 18 | 19 | ```sh 20 | $ git clone git@github.com:Quick/Quick.git 21 | $ rake templates:install 22 | ``` 23 | 24 | Uninstalling is easy, too: 25 | 26 | ```sh 27 | $ rake templates:uninstall 28 | ``` 29 | -------------------------------------------------------------------------------- /Documentation/InstallingQuick.md: -------------------------------------------------------------------------------- 1 | # Installing Quick 2 | 3 | > **If you're using Xcode 6.2 & Swift 1.1,** use Quick `v0.2.*`. 4 | > New releases are developed on the `swift-1.1` branch. 5 | > 6 | > **If you're using Xcode 6.3 & Swift 1.2,** use Quick `v0.3.*`. 7 | > New releases are developed on the `master` branch. 8 | > 9 | > **If you're using Xcode 7.0 & Swift 2.0,** use the latest version of Quick--`v0.6.0` at the time of writing. 10 | > New releases are developed on the `swift-2.0` branch. 11 | 12 | 13 | 14 | Quick provides the syntax to define examples and example groups. Nimble 15 | provides the `expect(...).to` assertion syntax. You may use either one, 16 | or both, in your tests. 17 | 18 | There are three recommended ways of linking Quick to your tests: 19 | 20 | 1. [Git Submodules](#git-submodules) 21 | 2. [CocoaPods](#cocoapods) 22 | 3. [Carthage](#carthage) 23 | 24 | Choose one and follow the instructions below. Once you've completed them, 25 | you should be able to `import Quick` from within files in your test target. 26 | 27 | ## Git Submodules 28 | 29 | To link Quick and Nimble using Git submodules: 30 | 31 | 1. Add submodule for Quick. 32 | 2. If you don't already have a `.xcworkspace` for your project, create one. ([Here's how](https://developer.apple.com/library/ios/recipes/xcode_help-structure_navigator/articles/Adding_an_Existing_Project_to_a_Workspace.html)) 33 | 3. Add `Quick.xcodeproj` to your project's `.xcworkspace`. 34 | 4. Add `Nimble.xcodeproj` to your project's `.xcworkspace`. It exists in `path/to/Quick/Externals/Nimble`. By adding Nimble from Quick's dependencies (as opposed to adding directly as a submodule), you'll ensure that you're using the correct version of Nimble for whatever version of Quick you're using. 35 | 5. Link `Quick.framework` and `Nimble.framework` in your test target's 36 | "Link Binary with Libraries" build phase. 37 | 38 | First, if you don't already have one, create a directory for your Git submodules. 39 | Let's assume you have a directory named `Vendor`. 40 | 41 | **Step One:** Download Quick and Nimble as Git submodules: 42 | 43 | ```sh 44 | git submodule add git@github.com:Quick/Quick.git Vendor/Quick 45 | git submodule add git@github.com:Quick/Nimble.git Vendor/Nimble 46 | git submodule update --init --recursive 47 | ``` 48 | 49 | **Step Two:** Add the `Quick.xcodeproj` and `Nimble.xcodeproj` files downloaded above to 50 | your project's `.xcworkspace`. For example, this is `Guanaco.xcworkspace`, the 51 | workspace for a project that is tested using Quick and Nimble: 52 | 53 | ![](http://f.cl.ly/items/2b2R0e1h09003u2f0Z3U/Screen%20Shot%202015-02-27%20at%202.19.37%20PM.png) 54 | 55 | **Step Three:** Link the `Quick.framework` during your test target's 56 | `Link Binary with Libraries` build phase. You should see two 57 | `Quick.frameworks`; one is for OS X, and the other is for iOS. 58 | 59 | ![](http://cl.ly/image/2L0G0H1a173C/Screen%20Shot%202014-06-08%20at%204.27.48%20AM.png) 60 | 61 | Do the same for the `Nimble.framework`, and you're done! 62 | 63 | **Updating the Submodules:** If you ever want to update the Quick 64 | or Nimble submodules to latest version, enter the Quick directory 65 | and pull from the master repository: 66 | 67 | ```sh 68 | cd /path/to/your/project/Vendor/Quick 69 | git checkout master 70 | git pull --rebase origin master 71 | ``` 72 | 73 | Your Git repository will track changes to submodules. You'll want to 74 | commit the fact that you've updated the Quick submodule: 75 | 76 | ```sh 77 | cd /path/to/your/project 78 | git commit -m "Updated Quick submodule" 79 | ``` 80 | 81 | **Cloning a Repository that Includes a Quick Submodule:** After other people 82 | clone your repository, they'll have to pull down the submodules as well. 83 | They can do so by running the `git submodule update` command: 84 | 85 | ```sh 86 | git submodule update --init --recursive 87 | ``` 88 | 89 | You can read more about Git submodules [here](http://git-scm.com/book/en/Git-Tools-Submodules). 90 | 91 | ## CocoaPods 92 | 93 | First, update CocoaPods to Version 0.36.0 or newer, which is necessary to install CocoaPods using Swift. 94 | 95 | Then, add Quick and Nimble to your Podfile. Additionally, the ```use_frameworks!``` line is necessary for using Swift in CocoaPods: 96 | 97 | ```rb 98 | 99 | # Podfile 100 | 101 | use_frameworks! 102 | 103 | def testing_pods 104 | # If you're using Xcode 7 / Swift 2 105 | pod 'Quick', '~> 0.6.0' 106 | pod 'Nimble', '2.0.0-rc.2' 107 | 108 | # If you're using Xcode 6 / Swift 1.2 109 | pod 'Quick', '~> 0.3.0' 110 | pod 'Nimble', '~> 1.0.0' 111 | end 112 | 113 | target 'MyTests' do 114 | testing_pods 115 | end 116 | 117 | target 'MyUITests' do 118 | testing_pods 119 | end 120 | ``` 121 | 122 | Finally, download and link Quick and Nimble to your tests: 123 | 124 | ```sh 125 | pod install 126 | ``` 127 | 128 | ### Using Swift 1.2? 129 | 130 | The latest release of Quick (0.4.0) is for Swift 2 (Xcode 7), but the latest Nimble (1.0.0) is for Swift 1.2 (Xcode 6). 131 | 132 | If you want Xcode 6 do: 133 | 134 | ```sh 135 | target 'MyTests' do 136 | use_frameworks! 137 | pod 'Quick', '~>0.3.0' 138 | pod 'Nimble', '~>1.0.0' 139 | end 140 | ``` 141 | 142 | ## [Carthage](https://github.com/Carthage/Carthage) 143 | 144 | As test targets do not have the "Embedded Binaries" section, the frameworks must 145 | be added to the target's "Link Binary With Libraries" as well as a "Copy Files" build phase 146 | to copy them to the target's Frameworks destination. 147 | 148 | > As Carthage builds dynamic frameworks, you will need a valid code signing identity set up. 149 | 150 | 1. Add Quick to your [`Cartfile.private`](https://github.com/Carthage/Carthage/blob/master/Documentation/Artifacts.md#cartfileprivate): 151 | 152 | ``` 153 | github "Quick/Quick" 154 | github "Quick/Nimble" 155 | ``` 156 | 157 | 2. Run `carthage update`. 158 | 3. From your `Carthage/Build/[platform]/` directory, add both Quick and Nimble to your test target's "Link Binary With Libraries" build phase: 159 | ![](http://i.imgur.com/pBkDDk5.png) 160 | 161 | 4. For your test target, create a new build phase of type "Copy Files": 162 | ![](http://i.imgur.com/jZATIjQ.png) 163 | 164 | 5. Set the "Destination" to "Frameworks", then add both frameworks: 165 | ![](http://i.imgur.com/rpnyWGH.png) 166 | 167 | This is not "the one and only way" to use Carthage to manage dependencies. 168 | For further reference check out the [Carthage documentation](https://github.com/Carthage/Carthage/blob/master/README.md). 169 | 170 | ### (Not Recommended) Running Quick Specs on a Physical iOS Device 171 | 172 | In order to run specs written in Quick on device, you need to add `Quick.framework` and 173 | `Nimble.framework` as `Embedded Binaries` to the `Host Application` of the 174 | test target. After adding a framework as an embedded binary, Xcode will 175 | automatically link the host app against the framework. 176 | 177 | ![](http://indiedev.kapsi.fi/images/embed-in-host.png) 178 | -------------------------------------------------------------------------------- /Documentation/MoreResources.md: -------------------------------------------------------------------------------- 1 | # More Resources 2 | 3 | ## Examples of Quick Specs 4 | 5 | Quick is used by many companies, open-source projects, and individuals, 6 | including [GitHub](https://github.com/github) and 7 | [ReactiveCocoa](https://github.com/ReactiveCocoa). For examples, check out: 8 | 9 | - https://github.com/ReactiveCocoa/ReactiveCocoa 10 | - https://github.com/github/Archimedes 11 | - https://github.com/libgit2/objective-git 12 | - https://github.com/jspahrsummers/RXSwift 13 | - https://github.com/artsy/eidolon 14 | - https://github.com/AshFurrow/Moya 15 | - https://github.com/nerdyc/Squeal 16 | - https://github.com/pepibumur/SugarRecord 17 | 18 | ## More on Unit Testing for OS X and iOS Apps 19 | 20 | - **[Quality Coding](http://qualitycoding.org/)**: 21 | A blog on iOS development that focuses on unit testing. 22 | - **[OCMock Tutorials](http://ocmock.org/tutorials/)**: 23 | Use OCMock when you need "fake objects" in your tests. 24 | - **[Nocilla: Stunning HTTP stubbing for iOS and Mac OS X](https://github.com/luisobo/Nocilla)**: 25 | Use this library to test code that sends requests to, and receives responses from, the Internet. 26 | - **[Pivotal Labs: Writing Beautiful Specs with Jasmine Custom Matchers](http://pivotallabs.com/writing-beautiful-specs-jasmine-custom-matchers/)**: 27 | See [the Nimble documentation](https://github.com/Quick/Nimble) for instructions on how to write 28 | custom matchers in Nimble. 29 | -------------------------------------------------------------------------------- /Documentation/NimbleAssertions.md: -------------------------------------------------------------------------------- 1 | # Clearer Tests Using Nimble Assertions 2 | 3 | When code doesn't work the way it's supposed to, unit tests should make it 4 | **clear** exactly what's wrong. 5 | 6 | Take the following function which, given a bunch of monkeys, only returns 7 | the silly monkeys in the bunch: 8 | 9 | ```swift 10 | public func silliest(monkeys: [Monkey]) -> [Monkey] { 11 | return monkeys.filter { $0.silliness == .VerySilly } 12 | } 13 | ``` 14 | 15 | Now let's say we have a unit test for this function: 16 | 17 | ```swift 18 | func testSilliest_whenMonkeysContainSillyMonkeys_theyreIncludedInTheResult() { 19 | let kiki = Monkey(name: "Kiki", silliness: .ExtremelySilly) 20 | let carl = Monkey(name: "Carl", silliness: .NotSilly) 21 | let jane = Monkey(name: "Jane", silliness: .VerySilly) 22 | let sillyMonkeys = silliest([kiki, carl, jane]) 23 | XCTAssertTrue(contains(sillyMonkeys, kiki)) 24 | } 25 | ``` 26 | 27 | The test fails with the following failure message: 28 | 29 | ``` 30 | XCTAssertTrue failed 31 | ``` 32 | 33 | ![](http://f.cl.ly/items/1G17453p47090y30203d/Screen%20Shot%202015-02-26%20at%209.08.27%20AM.png) 34 | 35 | The failure message leaves a lot to be desired. It leaves us wondering, 36 | "OK, so something that should have been true was false--but what?" 37 | That confusion slows us down, since we now have to spend time deciphering test code. 38 | 39 | ## Better Failure Messages, Part 1: Manually Providing `XCTAssert` Failure Messages 40 | 41 | `XCTAssert` assertions allow us to specify a failure message of our own, which certainly helps: 42 | 43 | ```diff 44 | func testSilliest_whenMonkeysContainSillyMonkeys_theyreIncludedInTheResult() { 45 | let kiki = Monkey(name: "Kiki", silliness: .ExtremelySilly) 46 | let carl = Monkey(name: "Carl", silliness: .NotSilly) 47 | let jane = Monkey(name: "Jane", silliness: .VerySilly) 48 | let sillyMonkeys = silliest([kiki, carl, jane]) 49 | - XCTAssertTrue(contains(sillyMonkeys, kiki)) 50 | + XCTAssertTrue(contains(sillyMonkeys, kiki), "Expected sillyMonkeys to contain 'Kiki'") 51 | } 52 | ``` 53 | 54 | But we have to write our own failure message. 55 | 56 | ## Better Failure Messages, Part 2: Nimble Failure Messages 57 | 58 | Nimble makes your test assertions, and their failure messages, easier to read: 59 | 60 | ```diff 61 | func testSilliest_whenMonkeysContainSillyMonkeys_theyreIncludedInTheResult() { 62 | let kiki = Monkey(name: "Kiki", silliness: .ExtremelySilly) 63 | let carl = Monkey(name: "Carl", silliness: .NotSilly) 64 | let jane = Monkey(name: "Jane", silliness: .VerySilly) 65 | let sillyMonkeys = silliest([kiki, carl, jane]) 66 | - XCTAssertTrue(contains(sillyMonkeys, kiki), "Expected sillyMonkeys to contain 'Kiki'") 67 | + expect(sillyMonkeys).to(contain(kiki)) 68 | } 69 | ``` 70 | 71 | We don't have to write our own failure message--the one provided by Nimble 72 | is already very readable: 73 | 74 | ``` 75 | expected to contain , 76 | got <[Monkey(name: Jane, silliness: VerySilly)]> 77 | ``` 78 | 79 | ![](http://f.cl.ly/items/3N2e3g2K3W123b1L1J0G/Screen%20Shot%202015-02-26%20at%2011.27.02%20AM.png) 80 | 81 | The failure message makes it clear what's wrong: we were expecting `kiki` to be included 82 | in the result of `silliest()`, but the result only contains `jane`. Now that we know 83 | exactly what's wrong, it's easy to fix the issue: 84 | 85 | ```diff 86 | public func silliest(monkeys: [Monkey]) -> [Monkey] { 87 | - return monkeys.filter { $0.silliness == .VerySilly } 88 | + return monkeys.filter { $0.silliness == .VerySilly || $0.silliness == .ExtremelySilly } 89 | } 90 | ``` 91 | 92 | Nimble provides many different kind of assertions, each with great failure 93 | messages. And unlike `XCTAssert`, you don't have to type your own failure message 94 | every time. 95 | 96 | For the full list of Nimble assertions, check out the [Nimble README](https://github.com/Quick/Nimble). 97 | Below is just a sample, to whet your appetite: 98 | 99 | ```swift 100 | expect(1 + 1).to(equal(2)) 101 | expect(1.2).to(beCloseTo(1.1, within: 0.1)) 102 | expect(3) > 2 103 | expect("seahorse").to(contain("sea")) 104 | expect(["Atlantic", "Pacific"]).toNot(contain("Mississippi")) 105 | expect(ocean.isClean).toEventually(beTruthy()) 106 | ``` 107 | -------------------------------------------------------------------------------- /Documentation/QuickExamplesAndGroups.md: -------------------------------------------------------------------------------- 1 | # Organized Tests with Quick Examples and Example Groups 2 | 3 | Quick uses a special syntax to define **examples** and **example groups**. 4 | 5 | In *[Effective Tests Using XCTest: Arrange, Act, and Assert](ArrangeActAssert.md)*, 6 | we learned that a good test method name is crucial--when a test starts failing, it's 7 | the best way to determine whether we have to fix the application code or update the test. 8 | 9 | Quick examples and example groups serve two purposes: 10 | 11 | 1. They encourage you to write descriptive test names. 12 | 2. They greatly simplify the test code in the "arrange" step of your tests. 13 | 14 | ## Examples Using `it` 15 | 16 | Examples, defined with the `it` function, use assertions to demonstrate 17 | how code should behave. These are like test methods in XCTest. 18 | 19 | `it` takes two parameters: the name of the example, and a closure. 20 | The examples below specify how the `Sea.Dolphin` class should behave. 21 | A new dolphin should be smart and friendly: 22 | 23 | ```swift 24 | // Swift 25 | 26 | import Quick 27 | import Nimble 28 | import Sea 29 | 30 | class DolphinSpec: QuickSpec { 31 | override func spec() { 32 | it("is friendly") { 33 | expect(Dolphin().isFriendly).to(beTruthy()) 34 | } 35 | 36 | it("is smart") { 37 | expect(Dolphin().isSmart).to(beTruthy()) 38 | } 39 | } 40 | } 41 | ``` 42 | 43 | ```objc 44 | // Objective-C 45 | 46 | #import 47 | #import 48 | 49 | QuickSpecBegin(DolphinSpec) 50 | 51 | it(@"is friendly", ^{ 52 | expect(@([[Dolphin new] isFriendly])).to(beTruthy()); 53 | }); 54 | 55 | it(@"is smart", ^{ 56 | expect(@([[Dolphin new] isSmart])).to(beTruthy()); 57 | }); 58 | 59 | QuickSpecEnd 60 | ``` 61 | 62 | Use descriptions to make it clear what your examples are testing. 63 | Descriptions can be of any length and use any character, including 64 | characters from languages besides English, or even emoji! :v: :sunglasses: 65 | 66 | ## Example Groups Using `describe` and `context` 67 | 68 | Example groups are logical groupings of examples. Example groups can share 69 | setup and teardown code. 70 | 71 | ### Describing Classes and Methods Using `describe` 72 | 73 | To specify the behavior of the `Dolphin` class's `click` method--in 74 | other words, to test the method works--several `it` examples can be 75 | grouped together using the `describe` function. Grouping similar 76 | examples together makes the spec easier to read: 77 | 78 | ```swift 79 | // Swift 80 | 81 | import Quick 82 | import Nimble 83 | 84 | class DolphinSpec: QuickSpec { 85 | override func spec() { 86 | describe("a dolphin") { 87 | describe("its click") { 88 | it("is loud") { 89 | let click = Dolphin().click() 90 | expect(click.isLoud).to(beTruthy()) 91 | } 92 | 93 | it("has a high frequency") { 94 | let click = Dolphin().click() 95 | expect(click.hasHighFrequency).to(beTruthy()) 96 | } 97 | } 98 | } 99 | } 100 | } 101 | ``` 102 | 103 | ```objc 104 | // Objective-C 105 | 106 | #import 107 | #import 108 | 109 | QuickSpecBegin(DolphinSpec) 110 | 111 | describe(@"a dolphin", ^{ 112 | describe(@"its click", ^{ 113 | it(@"is loud", ^{ 114 | Click *click = [[Dolphin new] click]; 115 | expect(@(click.isLoud)).to(beTruthy()); 116 | }); 117 | 118 | it(@"has a high frequency", ^{ 119 | Click *click = [[Dolphin new] click]; 120 | expect(@(click.hasHighFrequency)).to(beTruthy()); 121 | }); 122 | }); 123 | }); 124 | 125 | QuickSpecEnd 126 | ``` 127 | 128 | When these two examples are run in Xcode, they'll display the 129 | description from the `describe` and `it` functions: 130 | 131 | 1. `DolphinSpec.a_dolphin_its_click_is_loud` 132 | 2. `DolphinSpec.a_dolphin_its_click_has_a_high_frequency` 133 | 134 | Again, it's clear what each of these examples is testing. 135 | 136 | ### Sharing Setup/Teardown Code Using `beforeEach` and `afterEach` 137 | 138 | Example groups don't just make the examples clearer, they're also useful 139 | for sharing setup and teardown code among examples in a group. 140 | 141 | In the example below, the `beforeEach` function is used to create a brand 142 | new instance of a dolphin and its click before each example in the group. 143 | This ensures that both are in a "fresh" state for every example: 144 | 145 | ```swift 146 | // Swift 147 | 148 | import Quick 149 | import Nimble 150 | 151 | class DolphinSpec: QuickSpec { 152 | override func spec() { 153 | describe("a dolphin") { 154 | var dolphin: Dolphin! 155 | beforeEach { 156 | dolphin = Dolphin() 157 | } 158 | 159 | describe("its click") { 160 | var click: Click! 161 | beforeEach { 162 | click = dolphin.click() 163 | } 164 | 165 | it("is loud") { 166 | expect(click.isLoud).to(beTruthy()) 167 | } 168 | 169 | it("has a high frequency") { 170 | expect(click.hasHighFrequency).to(beTruthy()) 171 | } 172 | } 173 | } 174 | } 175 | } 176 | ``` 177 | 178 | ```objc 179 | // Objective-C 180 | 181 | #import 182 | #import 183 | 184 | QuickSpecBegin(DolphinSpec) 185 | 186 | describe(@"a dolphin", ^{ 187 | __block Dolphin *dolphin = nil; 188 | beforeEach(^{ 189 | dolphin = [Dolphin new]; 190 | }); 191 | 192 | describe(@"its click", ^{ 193 | __block Click *click = nil; 194 | beforeEach(^{ 195 | click = [dolphin click]; 196 | }); 197 | 198 | it(@"is loud", ^{ 199 | expect(@(click.isLoud)).to(beTruthy()); 200 | }); 201 | 202 | it(@"has a high frequency", ^{ 203 | expect(@(click.hasHighFrequency)).to(beTruthy()); 204 | }); 205 | }); 206 | }); 207 | 208 | QuickSpecEnd 209 | ``` 210 | 211 | Sharing setup like this might not seem like a big deal with the 212 | dolphin example, but for more complicated objects, it saves a lot 213 | of typing! 214 | 215 | To execute code *after* each example, use `afterEach`. 216 | 217 | ### Specifying Conditional Behavior Using `context` 218 | 219 | Dolphins use clicks for echolocation. When they approach something 220 | particularly interesting to them, they release a series of clicks in 221 | order to get a better idea of what it is. 222 | 223 | The tests need to show that the `click` method behaves differently in 224 | different circumstances. Normally, the dolphin just clicks once. But when 225 | the dolphin is close to something interesting, it clicks several times. 226 | 227 | This can be expressed using `context` functions: one `context` for the 228 | normal case, and one `context` for when the dolphin is close to 229 | something interesting: 230 | 231 | ```swift 232 | // Swift 233 | 234 | import Quick 235 | import Nimble 236 | 237 | class DolphinSpec: QuickSpec { 238 | override func spec() { 239 | describe("a dolphin") { 240 | var dolphin: Dolphin! 241 | beforeEach { dolphin = Dolphin() } 242 | 243 | describe("its click") { 244 | context("when the dolphin is not near anything interesting") { 245 | it("is only emitted once") { 246 | expect(dolphin!.click().count).to(equal(1)) 247 | } 248 | } 249 | 250 | context("when the dolphin is near something interesting") { 251 | beforeEach { 252 | let ship = SunkenShip() 253 | Jamaica.dolphinCove.add(ship) 254 | Jamaica.dolphinCove.add(dolphin) 255 | } 256 | 257 | it("is emitted three times") { 258 | expect(dolphin.click().count).to(equal(3)) 259 | } 260 | } 261 | } 262 | } 263 | } 264 | } 265 | ``` 266 | 267 | ```objc 268 | // Objective-C 269 | 270 | #import 271 | #import 272 | 273 | QuickSpecBegin(DolphinSpec) 274 | 275 | describe(@"a dolphin", ^{ 276 | __block Dolphin *dolphin = nil; 277 | beforeEach(^{ dolphin = [Dolphin new]; }); 278 | 279 | describe(@"its click", ^{ 280 | context(@"when the dolphin is not near anything interesting", ^{ 281 | it(@"is only emitted once", ^{ 282 | expect(@([[dolphin click] count])).to(equal(@1)); 283 | }); 284 | }); 285 | 286 | context(@"when the dolphin is near something interesting", ^{ 287 | beforeEach(^{ 288 | [[Jamaica dolphinCove] add:[SunkenShip new]]; 289 | [[Jamaica dolphinCove] add:dolphin]; 290 | }); 291 | 292 | it(@"is emitted three times", ^{ 293 | expect(@([[dolphin click] count])).to(equal(@3)); 294 | }); 295 | }); 296 | }); 297 | }); 298 | 299 | QuickSpecEnd 300 | ``` 301 | 302 | ### Test Readability: Quick and XCTest 303 | 304 | In [Effective Tests Using XCTest: Arrange, Act, and Assert](ArrangeActAssert.md), 305 | we looked at how one test per condition was a great way to organize test code. 306 | In XCTest, that leads to long test method names: 307 | 308 | ```swift 309 | func testDolphin_click_whenTheDolphinIsNearSomethingInteresting_isEmittedThreeTimes() { 310 | // ... 311 | } 312 | ``` 313 | 314 | Using Quick, the conditions are much easier to read, and we can perform setup 315 | for each example group: 316 | 317 | ```swift 318 | describe("a dolphin") { 319 | describe("its click") { 320 | context("when the dolphin is near something interesting") { 321 | it("is emitted three times") { 322 | // ... 323 | } 324 | } 325 | } 326 | } 327 | ``` 328 | 329 | ## Temporarily Disabling Examples or Groups 330 | 331 | You can temporarily disable examples or example groups that don't pass yet. 332 | The names of the examples will be printed out along with the test results, 333 | but they won't be run. 334 | 335 | You can disable an example or group by prepending `x`: 336 | 337 | ```swift 338 | // Swift 339 | 340 | xdescribe("its click") { 341 | // ...none of the code in this closure will be run. 342 | } 343 | 344 | xcontext("when the dolphin is not near anything interesting") { 345 | // ...none of the code in this closure will be run. 346 | } 347 | 348 | xit("is only emitted once") { 349 | // ...none of the code in this closure will be run. 350 | } 351 | ``` 352 | 353 | ```objc 354 | // Objective-C 355 | 356 | xdescribe(@"its click", ^{ 357 | // ...none of the code in this closure will be run. 358 | }); 359 | 360 | xcontext(@"when the dolphin is not near anything interesting", ^{ 361 | // ...none of the code in this closure will be run. 362 | }); 363 | 364 | xit(@"is only emitted once", ^{ 365 | // ...none of the code in this closure will be run. 366 | }); 367 | ``` 368 | 369 | ## Temporarily Running a Subset of Focused Examples 370 | 371 | Sometimes it helps to focus on only one or a few examples. Running one 372 | or two examples is faster than the entire suite, after all. You can 373 | run only one or two by using the `fit` function. You can also focus a 374 | group of examples using `fdescribe` or `fcontext`: 375 | 376 | ```swift 377 | fit("is loud") { 378 | // ...only this focused example will be run. 379 | } 380 | 381 | it("has a high frequency") { 382 | // ...this example is not focused, and will not be run. 383 | } 384 | 385 | fcontext("when the dolphin is near something interesting") { 386 | // ...examples in this group are also focused, so they'll be run. 387 | } 388 | ``` 389 | 390 | ```objc 391 | fit(@"is loud", { 392 | // ...only this focused example will be run. 393 | }); 394 | 395 | it(@"has a high frequency", ^{ 396 | // ...this example is not focused, and will not be run. 397 | }); 398 | 399 | fcontext(@"when the dolphin is near something interesting", ^{ 400 | // ...examples in this group are also focused, so they'll be run. 401 | }); 402 | ``` 403 | 404 | ## Global Setup/Teardown Using `beforeSuite` and `afterSuite` 405 | 406 | Some test setup needs to be performed before *any* examples are 407 | run. For these cases, use `beforeSuite` and `afterSuite`. 408 | 409 | In the example below, a database of all the creatures in the ocean is 410 | created before any examples are run. That database is torn down once all 411 | the examples have finished: 412 | 413 | ```swift 414 | // Swift 415 | 416 | import Quick 417 | 418 | class DolphinSpec: QuickSpec { 419 | override func spec() { 420 | beforeSuite { 421 | OceanDatabase.createDatabase(name: "test.db") 422 | OceanDatabase.connectToDatabase(name: "test.db") 423 | } 424 | 425 | afterSuite { 426 | OceanDatabase.teardownDatabase(name: "test.db") 427 | } 428 | 429 | describe("a dolphin") { 430 | // ... 431 | } 432 | } 433 | } 434 | ``` 435 | 436 | ```objc 437 | // Objective-C 438 | 439 | #import 440 | 441 | QuickSpecBegin(DolphinSpec) 442 | 443 | beforeSuite(^{ 444 | [OceanDatabase createDatabase:@"test.db"]; 445 | [OceanDatabase connectToDatabase:@"test.db"]; 446 | }); 447 | 448 | afterSuite(^{ 449 | [OceanDatabase teardownDatabase:@"test.db"]; 450 | }); 451 | 452 | describe(@"a dolphin", ^{ 453 | // ... 454 | }); 455 | 456 | QuickSpecEnd 457 | ``` 458 | 459 | You can specify as many `beforeSuite` and `afterSuite` as you like. All 460 | `beforeSuite` closures will be executed before any tests run, and all 461 | `afterSuite` closures will be executed after all the tests are finished. 462 | There is no guarantee as to what order these closures will be executed in. 463 | 464 | ## Accessing Metadata for the Current Example 465 | 466 | There may be some cases in which you'd like the know the name of the example 467 | that is currently being run, or how many have been run so far. Quick provides 468 | access to this metadata in `beforeEach` and `afterEach` closures. 469 | 470 | ```swift 471 | beforeEach { exampleMetadata in 472 | println("Example number \(exampleMetadata.exampleIndex) is about to be run.") 473 | } 474 | 475 | afterEach { exampleMetadata in 476 | println("Example number \(exampleMetadata.exampleIndex) has run.") 477 | } 478 | ``` 479 | 480 | ```objc 481 | beforeEachWithMetadata(^(ExampleMetadata *exampleMetadata){ 482 | NSLog(@"Example number %l is about to be run.", (long)exampleMetadata.exampleIndex); 483 | }); 484 | 485 | afterEachWithMetadata(^(ExampleMetadata *exampleMetadata){ 486 | NSLog(@"Example number %l has run.", (long)exampleMetadata.exampleIndex); 487 | }); 488 | ``` 489 | -------------------------------------------------------------------------------- /Documentation/QuickInObjectiveC.md: -------------------------------------------------------------------------------- 1 | # Using Quick in Objective-C 2 | 3 | Quick works equally well in both Swift and Objective-C. 4 | 5 | There are two notes to keep in mind when using Quick in Objective-C, 6 | however, which are described below. 7 | 8 | ## The Optional Shorthand Syntax 9 | 10 | Importing Quick in an Objective-C file defines macros named `it` and 11 | `itShouldBehaveLike`, as well as functions like `context()` and `describe()`. 12 | 13 | If the project you are testing also defines symbols with these names, you may 14 | encounter confusing build failures. In that case, you can avoid namespace 15 | collision by turning off Quick's optional "shorthand" syntax: 16 | 17 | ```objc 18 | #define QUICK_DISABLE_SHORT_SYNTAX 1 19 | 20 | #import 21 | 22 | QuickSpecBegin(DolphinSpec) 23 | // ... 24 | QuickSpecEnd 25 | ``` 26 | 27 | You must define the `QUICK_DISABLE_SHORT_SYNTAX` macro *before* 28 | importing the Quick header. 29 | 30 | ## Your Test Target Must Include At Least One Swift File 31 | 32 | The Swift stdlib will not be linked into your test target, and thus 33 | Quick will fail to execute properly, if you test target does not contain 34 | *at least one* Swift file. 35 | 36 | Without at least one Swift file, your tests will exit prematurely with 37 | the following error: 38 | 39 | ``` 40 | *** Test session exited(82) without checking in. Executable cannot be 41 | loaded for some other reason, such as a problem with a library it 42 | depends on or a code signature/entitlements mismatch. 43 | ``` 44 | 45 | To fix the problem, add a blank file called `SwiftSpec.swift` to your test target: 46 | 47 | ```swift 48 | // SwiftSpec.swift 49 | 50 | import Quick 51 | ``` 52 | 53 | > For more details on this issue, see https://github.com/Quick/Quick/issues/164. 54 | -------------------------------------------------------------------------------- /Documentation/README.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | Quick helps you verify how your Swift and Objective-C programs behave. 4 | Doing so effectively isn't just a matter of knowing how to use Quick, 5 | however. The guides in this directory can help you write 6 | effective tests--not just using Quick, but even XCTest or other testing 7 | frameworks. 8 | 9 | Each guide covers a particular topic. If you're completely new to unit 10 | testing, consider reading them in the order they're introduced below: 11 | 12 | - **[Setting Up Tests in Your Xcode Project](SettingUpYourXcodeProject.md)**: 13 | Read this if you're having trouble using your application code from within 14 | your test files. 15 | - **[Effective Tests Using XCTest: Arrange, Act, and Assert](ArrangeActAssert.md)**: 16 | Read this to learn how to write `XCTestCase` tests that will help you write 17 | code faster and more effectively. 18 | - **[Don't Test Code, Instead Verify Behavior](BehavioralTesting.md)**: 19 | Read this to learn what kinds of tests speed you up, and which ones will only end up 20 | slowing you down. 21 | - **[Clearer Tests Using Nimble Assertions](NimbleAssertions.md)**: 22 | Read this to learn how to use Nimble to generate better failure messages. 23 | Better failure messages help you move faster, by spending less time figuring out why 24 | a test failed. 25 | - **[Organized Tests with Quick Examples and Example Groups](QuickExamplesAndGroups.md)**: 26 | Read this to learn how Quick can help you write even more effective tests, using 27 | *examples* and *example groups*. 28 | - **[Testing OS X and iOS Applications](TestingApps.md)**: 29 | Read this to learn more about testing code that uses the AppKit and UIKit frameworks. 30 | - **[Reducing Test Boilerplate with Shared Assertions](SharedExamples.md)** 31 | Read this to learn how to share sets of assertions among your tests. 32 | - **[Configuring How Quick Behaves](ConfiguringQuick.md)**: 33 | Read this to learn how you can change how Quick behaves when running your test suite. 34 | - **[Using Quick in Objective-C](QuickInObjectiveC.md)**: 35 | Read this if you experience trouble using Quick in Objective-C. 36 | - **[Installing Quick](InstallingQuick.md)**: 37 | Read this for instructions on how to add Quick to your project, using 38 | Git submodules, CocoaPods, or Carthage. 39 | - **[Installing Quick File Templates](InstallingFileTemplates.md)**: 40 | Read this to learn how to install file templates that make writing Quick specs faster. 41 | - **[More Resources](MoreResources.md)** 42 | A list of additional resources on OS X and iOS testing. 43 | -------------------------------------------------------------------------------- /Documentation/SettingUpYourXcodeProject.md: -------------------------------------------------------------------------------- 1 | # Setting Up Tests in Your Xcode Project 2 | 3 | When you create a new project in Xcode 6, a unit test target is included 4 | by default. To write unit tests, you'll need to be able to use your main 5 | target's code from within your test target. 6 | 7 | ## Testing Swift Code Using Swift 8 | 9 | In order to test code written in Swift, you'll need to do three things: 10 | 11 | 1. Set "defines module" in your `.xcodeproj` to `YES`. 12 | 13 | * To do this in Xcode: Choose your project, then "Build Settings" header, then "Defines Modules" line, then select "Yes". 14 | 15 | 2. Mark any class/method/function you want to test `public`, since only 16 | `public` symbols are exported. 17 | 3. `import YourAppModuleName` in your unit tests. 18 | 19 | ```swift 20 | // MyAppTests.swift 21 | 22 | import XCTest 23 | import MyModule 24 | 25 | class MyClassTests: XCTestCase { 26 | // ... 27 | } 28 | ``` 29 | 30 | > Some developers advocate adding Swift source files to your test target. 31 | However, this leads to [subtle, hard-to-diagnose 32 | errors](https://github.com/Quick/Quick/issues/91), and is not 33 | recommended. 34 | 35 | ## Testing Objective-C Code Using Swift 36 | 37 | 1. Add a bridging header to your test target. 38 | 2. In the bridging header, import the file containing the code you'd like to test. 39 | 40 | ```objc 41 | // MyAppTests-BridgingHeader.h 42 | 43 | #import "MyClass.h" 44 | ``` 45 | 46 | You can now use the code from `MyClass.h` in your Swift test files. 47 | 48 | ## Testing Swift Code Using Objective-C 49 | 50 | 1. Bridge Swift classes and functions you'd like to test to Objective-C by 51 | using the `@objc` attribute. 52 | 2. Import your module's Swift headers in your unit tests. 53 | 54 | ```objc 55 | #import 56 | #import "MyModule-Swift.h" 57 | 58 | @interface MyClassTests: XCTestCase 59 | // ... 60 | @end 61 | ``` 62 | 63 | ## Testing Objective-C Code Using Objective-C 64 | 65 | Import the file defining the code you'd like to test from within your test target: 66 | 67 | ```objc 68 | // MyAppTests.m 69 | 70 | #import 71 | #import "MyClass.h" 72 | 73 | @interface MyClassTests: XCTestCase 74 | // ... 75 | @end 76 | ``` 77 | -------------------------------------------------------------------------------- /Documentation/SharedExamples.md: -------------------------------------------------------------------------------- 1 | # Reducing Test Boilerplate with Shared Assertions 2 | 3 | In some cases, the same set of specifications apply to multiple objects. 4 | 5 | For example, consider a protocol called `Edible`. When a dolphin 6 | eats something `Edible`, the dolphin becomes happy. `Mackerel` and 7 | `Cod` are both edible. Quick allows you to easily test that a dolphin is 8 | happy to eat either one. 9 | 10 | The example below defines a set of "shared examples" for "something edible", 11 | and specifies that both mackerel and cod behave like "something edible": 12 | 13 | ```swift 14 | // Swift 15 | 16 | import Quick 17 | import Nimble 18 | 19 | class EdibleSharedExamplesConfiguration: QuickConfiguration { 20 | override class func configure(configuration: Configuration) { 21 | sharedExamples("something edible") { (sharedExampleContext: SharedExampleContext) in 22 | it("makes dolphins happy") { 23 | let dolphin = Dolphin(happy: false) 24 | let edible = sharedExampleContext()["edible"] 25 | dolphin.eat(edible) 26 | expect(dolphin.isHappy).to(beTruthy()) 27 | } 28 | } 29 | } 30 | } 31 | 32 | class MackerelSpec: QuickSpec { 33 | override func spec() { 34 | var mackerel: Mackerel! 35 | beforeEach { 36 | mackerel = Mackerel() 37 | } 38 | 39 | itBehavesLike("something edible") { ["edible": mackerel] } 40 | } 41 | } 42 | 43 | class CodSpec: QuickSpec { 44 | override func spec() { 45 | var cod: Cod! 46 | beforeEach { 47 | cod = Cod() 48 | } 49 | 50 | itBehavesLike("something edible") { ["edible": cod] } 51 | } 52 | } 53 | ``` 54 | 55 | ```objc 56 | // Objective-C 57 | 58 | #import 59 | #import 60 | 61 | QuickConfigurationBegin(EdibleSharedExamplesConfiguration) 62 | 63 | + (void)configure:(Configuration *configuration) { 64 | sharedExamples(@"something edible", ^(QCKDSLSharedExampleContext exampleContext) { 65 | it(@"makes dolphins happy") { 66 | Dolphin *dolphin = [[Dolphin alloc] init]; 67 | dolphin.happy = NO; 68 | id edible = exampleContext()[@"edible"]; 69 | [dolphin eat:edible]; 70 | expect(dolphin.isHappy).to(beTruthy()) 71 | } 72 | }); 73 | } 74 | 75 | QuickConfigurationEnd 76 | 77 | QuickSpecBegin(MackerelSpec) 78 | 79 | __block Mackerel *mackerel = nil; 80 | beforeEach(^{ 81 | mackerel = [[Mackerel alloc] init]; 82 | }); 83 | 84 | itBehavesLike(@"someting edible", ^{ return @{ @"edible": mackerel }; }); 85 | 86 | QuickSpecEnd 87 | 88 | QuickSpecBegin(CodSpec) 89 | 90 | __block Mackerel *cod = nil; 91 | beforeEach(^{ 92 | cod = [[Cod alloc] init]; 93 | }); 94 | 95 | itBehavesLike(@"someting edible", ^{ return @{ @"edible": cod }; }); 96 | 97 | QuickSpecEnd 98 | ``` 99 | 100 | Shared examples can include any number of `it`, `context`, and 101 | `describe` blocks. They save a *lot* of typing when running 102 | the same tests against several different kinds of objects. 103 | 104 | In some cases, you won't need any additional context. In Swift, you can 105 | simply use `sharedExampleFor` closures that take no parameters. This 106 | might be useful when testing some sort of global state: 107 | 108 | ```swift 109 | // Swift 110 | 111 | import Quick 112 | 113 | sharedExamplesFor("everything under the sea") { 114 | // ... 115 | } 116 | 117 | itBehavesLike("everything under the sea") 118 | ``` 119 | 120 | > In Objective-C, you'll have to pass a block that takes a 121 | `QCKDSLSharedExampleContext`, even if you don't plan on using that 122 | argument. Sorry, but that's the way the cookie crumbles! 123 | :cookie: :bomb: 124 | 125 | You can also "focus" shared examples using the `fitBehavesLike` function. 126 | -------------------------------------------------------------------------------- /Documentation/TestingApps.md: -------------------------------------------------------------------------------- 1 | # Testing OS X and iOS Applications 2 | 3 | *[Setting Up Tests in Your Xcode Project](SettingUpYourXcodeProject.md)* 4 | covers everything you need to know to test any Objective-C or Swift function or class. 5 | In this section, we'll go over a few additional hints for testing 6 | classes like `UIViewController` subclasses. 7 | 8 | > You can see a short lightning talk covering most of these topics 9 | [here](https://vimeo.com/115671189#t=37m50s) (the talk begins at 37'50"). 10 | 11 | ## Triggering `UIViewController` Lifecycle Events 12 | 13 | Normally, UIKit triggers lifecycle events for your view controller as it's 14 | presented within the app. When testing a `UIViewController`, however, you'll 15 | need to trigger these yourself. You can do so in one of three ways: 16 | 17 | 1. Accessing `UIViewController.view`, which triggers things like `UIViewController.viewDidLoad()`. 18 | 2. Use `UIViewController.beginAppearanceTransition()` to trigger most lifecycle events. 19 | 3. Directly calling methods like `UIViewController.viewDidLoad()` or `UIViewController.viewWillAppear()`. 20 | 21 | ```swift 22 | // Swift 23 | 24 | import Quick 25 | import Nimble 26 | import BananaApp 27 | 28 | class BananaViewControllerSpec: QuickSpec { 29 | override func spec() { 30 | var viewController: BananaViewController! 31 | beforeEach { 32 | viewController = BananaViewController() 33 | } 34 | 35 | describe(".viewDidLoad()") { 36 | beforeEach { 37 | // Method #1: Access the view to trigger BananaViewController.viewDidLoad(). 38 | let _ = viewController.view 39 | } 40 | 41 | it("sets the banana count label to zero") { 42 | // Since the label is only initialized when the view is loaded, this 43 | // would fail if we didn't access the view in the `beforeEach` above. 44 | expect(viewController.bananaCountLabel.text).to(equal("0")) 45 | } 46 | } 47 | 48 | describe("the view") { 49 | beforeEach { 50 | // Method #2: Triggers .viewDidLoad(), .viewWillAppear(), and .viewDidAppear() events. 51 | viewController.beginAppearanceTransition(true, animated: false) 52 | viewController.endAppearanceTransition() 53 | } 54 | // ... 55 | } 56 | 57 | describe(".viewWillDisappear()") { 58 | beforeEach { 59 | // Method #3: Directly call the lifecycle event. 60 | viewController.viewWillDisappear(false) 61 | } 62 | // ... 63 | } 64 | } 65 | } 66 | ``` 67 | 68 | ```objc 69 | // Objective-C 70 | 71 | #import 72 | #import 73 | #import "BananaViewController.h" 74 | 75 | QuickSpecBegin(BananaViewControllerSpec) 76 | 77 | __block BananaViewController *viewController = nil; 78 | beforeEach(^{ 79 | viewController = [[BananaViewController alloc] init]; 80 | }); 81 | 82 | describe(@"-viewDidLoad", ^{ 83 | beforeEach(^{ 84 | // Method #1: Access the view to trigger -[BananaViewController viewDidLoad]. 85 | [viewController view]; 86 | }); 87 | 88 | it(@"sets the banana count label to zero", ^{ 89 | // Since the label is only initialized when the view is loaded, this 90 | // would fail if we didn't access the view in the `beforeEach` above. 91 | expect(viewController.bananaCountLabel.text).to(equal(@"0")) 92 | }); 93 | }); 94 | 95 | describe(@"the view", ^{ 96 | beforeEach(^{ 97 | // Method #2: Triggers .viewDidLoad(), .viewWillAppear(), and .viewDidAppear() events. 98 | [viewController beginAppearanceTransition:YES animated:NO]; 99 | [viewController endAppearanceTransition]; 100 | }); 101 | // ... 102 | }); 103 | 104 | describe(@"-viewWillDisappear", ^{ 105 | beforeEach(^{ 106 | // Method #3: Directly call the lifecycle event. 107 | [viewController viewWillDisappear:NO]; 108 | }); 109 | // ... 110 | }); 111 | 112 | QuickSpecEnd 113 | ``` 114 | 115 | ## Initializing View Controllers Defined in Storyboards 116 | 117 | To initialize view controllers defined in a storyboard, you'll need to assign 118 | a **Storyboard ID** to the view controller: 119 | 120 | ![](http://f.cl.ly/items/2X2G381K1h1l2B2Q0g3L/Screen%20Shot%202015-02-27%20at%2011.58.06%20AM.png) 121 | 122 | Once you've done so, you can instantiate the view controller from within your tests: 123 | 124 | ```swift 125 | // Swift 126 | 127 | var viewController: BananaViewController! 128 | beforeEach { 129 | // 1. Instantiate the storyboard. By default, it's name is "Main.storyboard". 130 | // You'll need to use a different string here if the name of your storyboard is different. 131 | let storyboard = UIStoryboard(name: "Main", bundle: nil) 132 | // 2. Use the storyboard to instantiate the view controller. 133 | viewController = 134 | storyboard.instantiateViewControllerWithIdentifier( 135 | "BananaViewControllerID") as! BananaViewController 136 | } 137 | ``` 138 | 139 | ```objc 140 | // Objective-C 141 | 142 | __block BananaViewController *viewController = nil; 143 | beforeEach(^{ 144 | // 1. Instantiate the storyboard. By default, it's name is "Main.storyboard". 145 | // You'll need to use a different string here if the name of your storyboard is different. 146 | UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; 147 | // 2. Use the storyboard to instantiate the view controller. 148 | viewController = [storyboard instantiateViewControllerWithIdentifier:@"BananaViewControllerID"]; 149 | }); 150 | ``` 151 | 152 | ## Triggering UIControl Events Like Button Taps 153 | 154 | Buttons and other UIKit classes inherit from `UIControl`, which defines methods 155 | that allow us to send control events, like button taps, programmatically. 156 | To test behavior that occurs when a button is tapped, you can write: 157 | 158 | ```swift 159 | // Swift 160 | 161 | describe("the 'more bananas' button") { 162 | it("increments the banana count label when tapped") { 163 | viewController.moreButton.sendActionsForControlEvents( 164 | UIControlEvents.TouchUpInside) 165 | expect(viewController.bananaCountLabel.text).to(equal("1")) 166 | } 167 | } 168 | ``` 169 | 170 | ```objc 171 | // Objective-C 172 | 173 | describe(@"the 'more bananas' button", ^{ 174 | it(@"increments the banana count label when tapped", ^{ 175 | [viewController.moreButton sendActionsForControlEvents:UIControlEventTouchUpInside]; 176 | expect(viewController.bananaCountLabel.text).to(equal(@"1")); 177 | }); 178 | }); 179 | ``` 180 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2014, Quick Team 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Quick Templates/Quick Configuration Class.xctemplate/Objective-C/___FILEBASENAME___.h: -------------------------------------------------------------------------------- 1 | @import Quick; 2 | 3 | @interface ___FILEBASENAMEASIDENTIFIER___ : QuickConfiguration 4 | 5 | @end 6 | 7 | -------------------------------------------------------------------------------- /Quick Templates/Quick Configuration Class.xctemplate/Objective-C/___FILEBASENAME___.m: -------------------------------------------------------------------------------- 1 | #import "___FILEBASENAMEASIDENTIFIER___.h" 2 | 3 | @implementation ___FILEBASENAMEASIDENTIFIER___ 4 | 5 | + (void)configure:(Configuration *)configuration { 6 | 7 | } 8 | 9 | @end 10 | 11 | -------------------------------------------------------------------------------- /Quick Templates/Quick Configuration Class.xctemplate/Swift/___FILEBASENAME___.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | 3 | class ___FILEBASENAMEASIDENTIFIER___: QuickConfiguration { 4 | override class func configure(configuration: Configuration) { 5 | 6 | } 7 | } 8 | 9 | -------------------------------------------------------------------------------- /Quick Templates/Quick Configuration Class.xctemplate/TemplateIcon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modocache/personal-fork-of-Quick/fdedd1e959ec4c464a45534475a0fde6610aa161/Quick Templates/Quick Configuration Class.xctemplate/TemplateIcon.icns -------------------------------------------------------------------------------- /Quick Templates/Quick Configuration Class.xctemplate/TemplateInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Kind 6 | Xcode.IDEKit.TextSubstitutionFileTemplateKind 7 | Description 8 | A QuickConfiguration subclass. 9 | Summary 10 | A QuickConfiguration subclass, overload +configure: to configure the behaviour when running specs, shared examples that are used across spec files. 11 | SortOrder 12 | 1 13 | BuildableType 14 | Test 15 | DefaultCompletionName 16 | Spec 17 | Options 18 | 19 | 20 | Description 21 | Name of the Quick Configuration 22 | Identifier 23 | productName 24 | Name 25 | QuickConfiguration Name: 26 | NotPersisted 27 | 28 | Required 29 | 30 | Type 31 | text 32 | 33 | 34 | AllowedTypes 35 | 36 | Swift 37 | 38 | public.swift-source 39 | 40 | Objective-C 41 | 42 | public.objective-c-source 43 | public.objective-c-plus-plus-source 44 | 45 | 46 | Default 47 | Swift 48 | Description 49 | The implementation language 50 | Identifier 51 | languageChoice 52 | MainTemplateFiles 53 | 54 | Objective-C 55 | ___FILEBASENAME___.m 56 | Swift 57 | ___FILEBASENAME___.swift 58 | 59 | Name 60 | Language: 61 | Required 62 | Yes 63 | Type 64 | popup 65 | Values 66 | 67 | Swift 68 | Objective-C 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /Quick Templates/Quick Spec Class.xctemplate/Objective-C/___FILEBASENAME___.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | QuickSpecBegin(___FILEBASENAMEASIDENTIFIER___) 5 | 6 | QuickSpecEnd 7 | -------------------------------------------------------------------------------- /Quick Templates/Quick Spec Class.xctemplate/Swift/___FILEBASENAME___.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | 4 | class ___FILEBASENAMEASIDENTIFIER___: QuickSpec { 5 | override func spec() { 6 | 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Quick Templates/Quick Spec Class.xctemplate/TemplateIcon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modocache/personal-fork-of-Quick/fdedd1e959ec4c464a45534475a0fde6610aa161/Quick Templates/Quick Spec Class.xctemplate/TemplateIcon.icns -------------------------------------------------------------------------------- /Quick Templates/Quick Spec Class.xctemplate/TemplateInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Kind 6 | Xcode.IDEKit.TextSubstitutionFileTemplateKind 7 | Description 8 | A class implementing a Quick spec. 9 | Summary 10 | A class implementing a Quick spec 11 | SortOrder 12 | 1 13 | BuildableType 14 | Test 15 | DefaultCompletionName 16 | Spec 17 | Options 18 | 19 | 20 | Description 21 | Name of the Quick spec class 22 | Identifier 23 | productName 24 | Name 25 | Spec Name: 26 | NotPersisted 27 | 28 | Required 29 | 30 | Type 31 | text 32 | 33 | 34 | AllowedTypes 35 | 36 | Swift 37 | 38 | public.swift-source 39 | 40 | Objective-C 41 | 42 | public.objective-c-source 43 | public.objective-c-plus-plus-source 44 | 45 | 46 | Default 47 | Swift 48 | Description 49 | The implementation language 50 | Identifier 51 | languageChoice 52 | MainTemplateFiles 53 | 54 | Objective-C 55 | ___FILEBASENAME___.m 56 | Swift 57 | ___FILEBASENAME___.swift 58 | 59 | Name 60 | Language: 61 | Required 62 | Yes 63 | Type 64 | popup 65 | Values 66 | 67 | Swift 68 | Objective-C 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /Quick.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "Quick" 3 | s.version = "0.6.0" 4 | s.summary = "The Swift (and Objective-C) testing framework." 5 | 6 | s.description = <<-DESC 7 | Quick is a behavior-driven development framework for Swift and Objective-C. Inspired by RSpec, Specta, and Ginkgo. 8 | DESC 9 | 10 | s.homepage = "https://github.com/Quick/Quick" 11 | s.license = { :type => "Apache 2.0", :file => "LICENSE" } 12 | 13 | s.author = "Quick Contributors" 14 | s.ios.deployment_target = "7.0" 15 | s.osx.deployment_target = "10.9" 16 | 17 | s.source = { :git => "https://github.com/Quick/Quick.git", :tag => "v#{s.version}" } 18 | s.source_files = "Quick", "Quick/**/*.{swift,h,m}" 19 | 20 | s.public_header_files = [ 21 | 'Quick/Configuration/QuickConfiguration.h', 22 | 'Quick/DSL/QCKDSL.h', 23 | 'Quick/Quick.h', 24 | 'Quick/QuickSpec.h', 25 | ] 26 | 27 | s.framework = "XCTest" 28 | s.requires_arc = true 29 | s.pod_target_xcconfig = { 'ENABLE_BITCODE' => 'NO' } 30 | end 31 | -------------------------------------------------------------------------------- /Quick.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Quick.xcodeproj/xcshareddata/xcschemes/Quick-OSX.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 74 | 75 | 81 | 82 | 83 | 84 | 85 | 86 | 92 | 93 | 99 | 100 | 101 | 102 | 104 | 105 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /Quick.xcodeproj/xcshareddata/xcschemes/Quick-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 74 | 75 | 81 | 82 | 83 | 84 | 85 | 86 | 92 | 93 | 99 | 100 | 101 | 102 | 104 | 105 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /Quick.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Quick/Callsite.swift: -------------------------------------------------------------------------------- 1 | /** 2 | An object encapsulating the file and line number at which 3 | a particular example is defined. 4 | */ 5 | final public class Callsite: NSObject { 6 | /** 7 | The absolute path of the file in which an example is defined. 8 | */ 9 | public let file: String 10 | 11 | /** 12 | The line number on which an example is defined. 13 | */ 14 | public let line: UInt 15 | 16 | internal init(file: String, line: UInt) { 17 | self.file = file 18 | self.line = line 19 | } 20 | } 21 | 22 | /** 23 | Returns a boolean indicating whether two Callsite objects are equal. 24 | If two callsites are in the same file and on the same line, they must be equal. 25 | */ 26 | public func ==(lhs: Callsite, rhs: Callsite) -> Bool { 27 | return lhs.file == rhs.file && lhs.line == rhs.line 28 | } 29 | -------------------------------------------------------------------------------- /Quick/Configuration/Configuration.swift: -------------------------------------------------------------------------------- 1 | /** 2 | A closure that temporarily exposes a Configuration object within 3 | the scope of the closure. 4 | */ 5 | public typealias QuickConfigurer = (configuration: Configuration) -> () 6 | 7 | /** 8 | A closure that, given metadata about an example, returns a boolean value 9 | indicating whether that example should be run. 10 | */ 11 | public typealias ExampleFilter = (example: Example) -> Bool 12 | 13 | /** 14 | A configuration encapsulates various options you can use 15 | to configure Quick's behavior. 16 | */ 17 | final public class Configuration: NSObject { 18 | internal let exampleHooks = ExampleHooks() 19 | internal let suiteHooks = SuiteHooks() 20 | internal var exclusionFilters: [ExampleFilter] = [{ example in 21 | if let pending = example.filterFlags[Filter.pending] { 22 | return pending 23 | } else { 24 | return false 25 | } 26 | }] 27 | internal var inclusionFilters: [ExampleFilter] = [{ example in 28 | if let focused = example.filterFlags[Filter.focused] { 29 | return focused 30 | } else { 31 | return false 32 | } 33 | }] 34 | 35 | /** 36 | Run all examples if none match the configured filters. True by default. 37 | */ 38 | public var runAllWhenEverythingFiltered = true 39 | 40 | /** 41 | Registers an inclusion filter. 42 | 43 | All examples are filtered using all inclusion filters. 44 | The remaining examples are run. If no examples remain, all examples are run. 45 | 46 | - parameter filter: A filter that, given an example, returns a value indicating 47 | whether that example should be included in the examples 48 | that are run. 49 | */ 50 | public func include(filter: ExampleFilter) { 51 | inclusionFilters.append(filter) 52 | } 53 | 54 | /** 55 | Registers an exclusion filter. 56 | 57 | All examples that remain after being filtered by the inclusion filters are 58 | then filtered via all exclusion filters. 59 | 60 | - parameter filter: A filter that, given an example, returns a value indicating 61 | whether that example should be excluded from the examples 62 | that are run. 63 | */ 64 | public func exclude(filter: ExampleFilter) { 65 | exclusionFilters.append(filter) 66 | } 67 | 68 | /** 69 | Identical to Quick.Configuration.beforeEach, except the closure is 70 | provided with metadata on the example that the closure is being run 71 | prior to. 72 | */ 73 | @objc(beforeEachWithMetadata:) 74 | public func beforeEach(closure: BeforeExampleWithMetadataClosure) { 75 | exampleHooks.appendBefore(closure) 76 | } 77 | 78 | /** 79 | Like Quick.DSL.beforeEach, this configures Quick to execute the 80 | given closure before each example that is run. The closure 81 | passed to this method is executed before each example Quick runs, 82 | globally across the test suite. You may call this method multiple 83 | times across mulitple +[QuickConfigure configure:] methods in order 84 | to define several closures to run before each example. 85 | 86 | Note that, since Quick makes no guarantee as to the order in which 87 | +[QuickConfiguration configure:] methods are evaluated, there is no 88 | guarantee as to the order in which beforeEach closures are evaluated 89 | either. Mulitple beforeEach defined on a single configuration, however, 90 | will be executed in the order they're defined. 91 | 92 | - parameter closure: The closure to be executed before each example 93 | in the test suite. 94 | */ 95 | public func beforeEach(closure: BeforeExampleClosure) { 96 | exampleHooks.appendBefore(closure) 97 | } 98 | 99 | /** 100 | Identical to Quick.Configuration.afterEach, except the closure 101 | is provided with metadata on the example that the closure is being 102 | run after. 103 | */ 104 | @objc(afterEachWithMetadata:) 105 | public func afterEach(closure: AfterExampleWithMetadataClosure) { 106 | exampleHooks.appendAfter(closure) 107 | } 108 | 109 | /** 110 | Like Quick.DSL.afterEach, this configures Quick to execute the 111 | given closure after each example that is run. The closure 112 | passed to this method is executed after each example Quick runs, 113 | globally across the test suite. You may call this method multiple 114 | times across mulitple +[QuickConfigure configure:] methods in order 115 | to define several closures to run after each example. 116 | 117 | Note that, since Quick makes no guarantee as to the order in which 118 | +[QuickConfiguration configure:] methods are evaluated, there is no 119 | guarantee as to the order in which afterEach closures are evaluated 120 | either. Mulitple afterEach defined on a single configuration, however, 121 | will be executed in the order they're defined. 122 | 123 | - parameter closure: The closure to be executed before each example 124 | in the test suite. 125 | */ 126 | public func afterEach(closure: AfterExampleClosure) { 127 | exampleHooks.appendAfter(closure) 128 | } 129 | 130 | /** 131 | Like Quick.DSL.beforeSuite, this configures Quick to execute 132 | the given closure prior to any and all examples that are run. 133 | The two methods are functionally equivalent. 134 | */ 135 | public func beforeSuite(closure: BeforeSuiteClosure) { 136 | suiteHooks.appendBefore(closure) 137 | } 138 | 139 | /** 140 | Like Quick.DSL.afterSuite, this configures Quick to execute 141 | the given closure after all examples have been run. 142 | The two methods are functionally equivalent. 143 | */ 144 | public func afterSuite(closure: AfterSuiteClosure) { 145 | suiteHooks.appendAfter(closure) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /Quick/Configuration/QuickConfiguration.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @class Configuration; 4 | 5 | /** 6 | Subclass QuickConfiguration and override the +[QuickConfiguration configure:] 7 | method in order to configure how Quick behaves when running specs, or to define 8 | shared examples that are used across spec files. 9 | */ 10 | @interface QuickConfiguration : NSObject 11 | 12 | /** 13 | This method is executed on each subclass of this class before Quick runs 14 | any examples. You may override this method on as many subclasses as you like, but 15 | there is no guarantee as to the order in which these methods are executed. 16 | 17 | You can override this method in order to: 18 | 19 | 1. Configure how Quick behaves, by modifying properties on the Configuration object. 20 | Setting the same properties in several methods has undefined behavior. 21 | 22 | 2. Define shared examples using `sharedExamples`. 23 | 24 | @param configuration A mutable object that is used to configure how Quick behaves on 25 | a framework level. For details on all the options, see the 26 | documentation in Configuration.swift. 27 | */ 28 | + (void)configure:(Configuration *)configuration; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /Quick/Configuration/QuickConfiguration.m: -------------------------------------------------------------------------------- 1 | #import "QuickConfiguration.h" 2 | #import "World.h" 3 | #import 4 | 5 | typedef void (^QCKClassEnumerationBlock)(Class klass); 6 | 7 | /** 8 | Finds all direct subclasses of the given class and passes them to the block provided. 9 | The classes are iterated over in the order that objc_getClassList returns them. 10 | 11 | @param klass The base class to find subclasses of. 12 | @param block A block that takes a Class. This block will be executed once for each subclass of klass. 13 | */ 14 | void qck_enumerateSubclasses(Class klass, QCKClassEnumerationBlock block) { 15 | Class *classes = NULL; 16 | int classesCount = objc_getClassList(NULL, 0); 17 | 18 | if (classesCount > 0) { 19 | classes = (Class *)calloc(sizeof(Class), classesCount); 20 | classesCount = objc_getClassList(classes, classesCount); 21 | 22 | Class subclass, superclass; 23 | for(int i = 0; i < classesCount; i++) { 24 | subclass = classes[i]; 25 | superclass = class_getSuperclass(subclass); 26 | if (superclass == klass && block) { 27 | block(subclass); 28 | } 29 | } 30 | 31 | free(classes); 32 | } 33 | } 34 | 35 | @implementation QuickConfiguration 36 | 37 | #pragma mark - Object Lifecycle 38 | 39 | /** 40 | QuickConfiguration is not meant to be instantiated; it merely provides a hook 41 | for users to configure how Quick behaves. Raise an exception if an instance of 42 | QuickConfiguration is created. 43 | */ 44 | - (instancetype)init { 45 | NSString *className = NSStringFromClass([self class]); 46 | NSString *selectorName = NSStringFromSelector(@selector(configure:)); 47 | [NSException raise:NSInternalInconsistencyException 48 | format:@"%@ is not meant to be instantiated; " 49 | @"subclass %@ and override %@ to configure Quick.", 50 | className, className, selectorName]; 51 | return nil; 52 | } 53 | 54 | #pragma mark - NSObject Overrides 55 | 56 | /** 57 | Hook into when QuickConfiguration is initialized in the runtime in order to 58 | call +[QuickConfiguration configure:] on each of its subclasses. 59 | */ 60 | + (void)initialize { 61 | // Only enumerate over the subclasses of QuickConfiguration, not any of its subclasses. 62 | if ([self class] == [QuickConfiguration class]) { 63 | 64 | // Only enumerate over subclasses once, even if +[QuickConfiguration initialize] 65 | // were to be called several times. This is necessary because +[QuickSpec initialize] 66 | // manually calls +[QuickConfiguration initialize]. 67 | static dispatch_once_t onceToken; 68 | dispatch_once(&onceToken, ^{ 69 | qck_enumerateSubclasses([QuickConfiguration class], ^(__unsafe_unretained Class klass) { 70 | [[World sharedWorld] configure:^(Configuration *configuration) { 71 | [klass configure:configuration]; 72 | }]; 73 | }); 74 | [[World sharedWorld] finalizeConfiguration]; 75 | }); 76 | } 77 | } 78 | 79 | #pragma mark - Public Interface 80 | 81 | + (void)configure:(Configuration *)configuration { } 82 | 83 | @end 84 | -------------------------------------------------------------------------------- /Quick/DSL/DSL.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Defines a closure to be run prior to any examples in the test suite. 3 | You may define an unlimited number of these closures, but there is no 4 | guarantee as to the order in which they're run. 5 | 6 | If the test suite crashes before the first example is run, this closure 7 | will not be executed. 8 | 9 | - parameter closure: The closure to be run prior to any examples in the test suite. 10 | */ 11 | public func beforeSuite(closure: BeforeSuiteClosure) { 12 | World.sharedWorld().beforeSuite(closure) 13 | } 14 | 15 | /** 16 | Defines a closure to be run after all of the examples in the test suite. 17 | You may define an unlimited number of these closures, but there is no 18 | guarantee as to the order in which they're run. 19 | 20 | If the test suite crashes before all examples are run, this closure 21 | will not be executed. 22 | 23 | - parameter closure: The closure to be run after all of the examples in the test suite. 24 | */ 25 | public func afterSuite(closure: AfterSuiteClosure) { 26 | World.sharedWorld().afterSuite(closure) 27 | } 28 | 29 | /** 30 | Defines a group of shared examples. These examples can be re-used in several locations 31 | by using the `itBehavesLike` function. 32 | 33 | - parameter name: The name of the shared example group. This must be unique across all shared example 34 | groups defined in a test suite. 35 | - parameter closure: A closure containing the examples. This behaves just like an example group defined 36 | using `describe` or `context`--the closure may contain any number of `beforeEach` 37 | and `afterEach` closures, as well as any number of examples (defined using `it`). 38 | */ 39 | public func sharedExamples(name: String, closure: () -> ()) { 40 | World.sharedWorld().sharedExamples(name, closure: { (NSDictionary) in closure() }) 41 | } 42 | 43 | /** 44 | Defines a group of shared examples. These examples can be re-used in several locations 45 | by using the `itBehavesLike` function. 46 | 47 | - parameter name: The name of the shared example group. This must be unique across all shared example 48 | groups defined in a test suite. 49 | - parameter closure: A closure containing the examples. This behaves just like an example group defined 50 | using `describe` or `context`--the closure may contain any number of `beforeEach` 51 | and `afterEach` closures, as well as any number of examples (defined using `it`). 52 | 53 | The closure takes a SharedExampleContext as an argument. This context is a function 54 | that can be executed to retrieve parameters passed in via an `itBehavesLike` function. 55 | */ 56 | public func sharedExamples(name: String, closure: SharedExampleClosure) { 57 | World.sharedWorld().sharedExamples(name, closure: closure) 58 | } 59 | 60 | /** 61 | Defines an example group. Example groups are logical groupings of examples. 62 | Example groups can share setup and teardown code. 63 | 64 | - parameter description: An arbitrary string describing the example group. 65 | - parameter closure: A closure that can contain other examples. 66 | - parameter flags: A mapping of string keys to booleans that can be used to filter examples or example groups. 67 | */ 68 | public func describe(description: String, flags: FilterFlags = [:], closure: () -> ()) { 69 | World.sharedWorld().describe(description, flags: flags, closure: closure) 70 | } 71 | 72 | /** 73 | Defines an example group. Equivalent to `describe`. 74 | */ 75 | public func context(description: String, flags: FilterFlags = [:], closure: () -> ()) { 76 | describe(description, flags: flags, closure: closure) 77 | } 78 | 79 | /** 80 | Defines a closure to be run prior to each example in the current example 81 | group. This closure is not run for pending or otherwise disabled examples. 82 | An example group may contain an unlimited number of beforeEach. They'll be 83 | run in the order they're defined, but you shouldn't rely on that behavior. 84 | 85 | - parameter closure: The closure to be run prior to each example. 86 | */ 87 | public func beforeEach(closure: BeforeExampleClosure) { 88 | World.sharedWorld().beforeEach(closure) 89 | } 90 | 91 | /** 92 | Identical to Quick.DSL.beforeEach, except the closure is provided with 93 | metadata on the example that the closure is being run prior to. 94 | */ 95 | public func beforeEach(closure: BeforeExampleWithMetadataClosure) { 96 | World.sharedWorld().beforeEach(closure: closure) 97 | } 98 | 99 | /** 100 | Defines a closure to be run after each example in the current example 101 | group. This closure is not run for pending or otherwise disabled examples. 102 | An example group may contain an unlimited number of afterEach. They'll be 103 | run in the order they're defined, but you shouldn't rely on that behavior. 104 | 105 | - parameter closure: The closure to be run after each example. 106 | */ 107 | public func afterEach(closure: AfterExampleClosure) { 108 | World.sharedWorld().afterEach(closure) 109 | } 110 | 111 | /** 112 | Identical to Quick.DSL.afterEach, except the closure is provided with 113 | metadata on the example that the closure is being run after. 114 | */ 115 | public func afterEach(closure: AfterExampleWithMetadataClosure) { 116 | World.sharedWorld().afterEach(closure: closure) 117 | } 118 | 119 | /** 120 | Defines an example. Examples use assertions to demonstrate how code should 121 | behave. These are like "tests" in XCTest. 122 | 123 | - parameter description: An arbitrary string describing what the example is meant to specify. 124 | - parameter closure: A closure that can contain assertions. 125 | - parameter flags: A mapping of string keys to booleans that can be used to filter examples or example groups. 126 | Empty by default. 127 | - parameter file: The absolute path to the file containing the example. A sensible default is provided. 128 | - parameter line: The line containing the example. A sensible default is provided. 129 | */ 130 | public func it(description: String, flags: FilterFlags = [:], file: String = __FILE__, line: UInt = __LINE__, closure: () -> ()) { 131 | World.sharedWorld().it(description, flags: flags, file: file, line: line, closure: closure) 132 | } 133 | 134 | /** 135 | Inserts the examples defined using a `sharedExamples` function into the current example group. 136 | The shared examples are executed at this location, as if they were written out manually. 137 | 138 | - parameter name: The name of the shared examples group to be executed. This must be identical to the 139 | name of a shared examples group defined using `sharedExamples`. If there are no shared 140 | examples that match the name given, an exception is thrown and the test suite will crash. 141 | - parameter flags: A mapping of string keys to booleans that can be used to filter examples or example groups. 142 | Empty by default. 143 | - parameter file: The absolute path to the file containing the current example group. A sensible default is provided. 144 | - parameter line: The line containing the current example group. A sensible default is provided. 145 | */ 146 | public func itBehavesLike(name: String, flags: FilterFlags = [:], file: String = __FILE__, line: UInt = __LINE__) { 147 | itBehavesLike(name, flags: flags, file: file, line: line, sharedExampleContext: { return [:] }) 148 | } 149 | 150 | /** 151 | Inserts the examples defined using a `sharedExamples` function into the current example group. 152 | The shared examples are executed at this location, as if they were written out manually. 153 | This function also passes those shared examples a context that can be evaluated to give the shared 154 | examples extra information on the subject of the example. 155 | 156 | - parameter name: The name of the shared examples group to be executed. This must be identical to the 157 | name of a shared examples group defined using `sharedExamples`. If there are no shared 158 | examples that match the name given, an exception is thrown and the test suite will crash. 159 | - parameter sharedExampleContext: A closure that, when evaluated, returns key-value pairs that provide the 160 | shared examples with extra information on the subject of the example. 161 | - parameter flags: A mapping of string keys to booleans that can be used to filter examples or example groups. 162 | Empty by default. 163 | - parameter file: The absolute path to the file containing the current example group. A sensible default is provided. 164 | - parameter line: The line containing the current example group. A sensible default is provided. 165 | */ 166 | public func itBehavesLike(name: String, flags: FilterFlags = [:], file: String = __FILE__, line: UInt = __LINE__, sharedExampleContext: SharedExampleContext) { 167 | World.sharedWorld().itBehavesLike(name, sharedExampleContext: sharedExampleContext, flags: flags, file: file, line: line) 168 | } 169 | 170 | /** 171 | Defines an example or example group that should not be executed. Use `pending` to temporarily disable 172 | examples or groups that should not be run yet. 173 | 174 | - parameter description: An arbitrary string describing the example or example group. 175 | - parameter closure: A closure that will not be evaluated. 176 | */ 177 | public func pending(description: String, closure: () -> ()) { 178 | World.sharedWorld().pending(description, closure: closure) 179 | } 180 | 181 | /** 182 | Use this to quickly mark a `describe` closure as pending. 183 | This disables all examples within the closure. 184 | */ 185 | public func xdescribe(description: String, flags: FilterFlags, closure: () -> ()) { 186 | World.sharedWorld().xdescribe(description, flags: flags, closure: closure) 187 | } 188 | 189 | /** 190 | Use this to quickly mark a `context` closure as pending. 191 | This disables all examples within the closure. 192 | */ 193 | public func xcontext(description: String, flags: FilterFlags, closure: () -> ()) { 194 | xdescribe(description, flags: flags, closure: closure) 195 | } 196 | 197 | /** 198 | Use this to quickly mark an `it` closure as pending. 199 | This disables the example and ensures the code within the closure is never run. 200 | */ 201 | public func xit(description: String, flags: FilterFlags = [:], file: String = __FILE__, line: UInt = __LINE__, closure: () -> ()) { 202 | World.sharedWorld().xit(description, flags: flags, file: file, line: line, closure: closure) 203 | } 204 | 205 | /** 206 | Use this to quickly focus a `describe` closure, focusing the examples in the closure. 207 | If any examples in the test suite are focused, only those examples are executed. 208 | This trumps any explicitly focused or unfocused examples within the closure--they are all treated as focused. 209 | */ 210 | public func fdescribe(description: String, flags: FilterFlags = [:], closure: () -> ()) { 211 | World.sharedWorld().fdescribe(description, flags: flags, closure: closure) 212 | } 213 | 214 | /** 215 | Use this to quickly focus a `context` closure. Equivalent to `fdescribe`. 216 | */ 217 | public func fcontext(description: String, flags: FilterFlags = [:], closure: () -> ()) { 218 | fdescribe(description, flags: flags, closure: closure) 219 | } 220 | 221 | /** 222 | Use this to quickly focus an `it` closure, focusing the example. 223 | If any examples in the test suite are focused, only those examples are executed. 224 | */ 225 | public func fit(description: String, flags: FilterFlags = [:], file: String = __FILE__, line: UInt = __LINE__, closure: () -> ()) { 226 | World.sharedWorld().fit(description, flags: flags, file: file, line: line, closure: closure) 227 | } 228 | -------------------------------------------------------------------------------- /Quick/DSL/QCKDSL.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @class ExampleMetadata; 4 | 5 | /** 6 | Provides a hook for Quick to be configured before any examples are run. 7 | Within this scope, override the +[QuickConfiguration configure:] method 8 | to set properties on a configuration object to customize Quick behavior. 9 | For details, see the documentation for Configuraiton.swift. 10 | 11 | @param name The name of the configuration class. Like any Objective-C 12 | class name, this must be unique to the current runtime 13 | environment. 14 | */ 15 | #define QuickConfigurationBegin(name) \ 16 | @interface name : QuickConfiguration; @end \ 17 | @implementation name \ 18 | 19 | 20 | /** 21 | Marks the end of a Quick configuration. 22 | Make sure you put this after `QuickConfigurationBegin`. 23 | */ 24 | #define QuickConfigurationEnd \ 25 | @end \ 26 | 27 | 28 | /** 29 | Defines a new QuickSpec. Define examples and example groups within the space 30 | between this and `QuickSpecEnd`. 31 | 32 | @param name The name of the spec class. Like any Objective-C class name, this 33 | must be unique to the current runtime environment. 34 | */ 35 | #define QuickSpecBegin(name) \ 36 | @interface name : QuickSpec; @end \ 37 | @implementation name \ 38 | - (void)spec { \ 39 | 40 | 41 | /** 42 | Marks the end of a QuickSpec. Make sure you put this after `QuickSpecBegin`. 43 | */ 44 | #define QuickSpecEnd \ 45 | } \ 46 | @end \ 47 | 48 | typedef NSDictionary *(^QCKDSLSharedExampleContext)(void); 49 | typedef void (^QCKDSLSharedExampleBlock)(QCKDSLSharedExampleContext); 50 | typedef void (^QCKDSLEmptyBlock)(void); 51 | typedef void (^QCKDSLExampleMetadataBlock)(ExampleMetadata *exampleMetadata); 52 | 53 | #define QUICK_EXPORT FOUNDATION_EXPORT 54 | 55 | QUICK_EXPORT void qck_beforeSuite(QCKDSLEmptyBlock closure); 56 | QUICK_EXPORT void qck_afterSuite(QCKDSLEmptyBlock closure); 57 | QUICK_EXPORT void qck_sharedExamples(NSString *name, QCKDSLSharedExampleBlock closure); 58 | QUICK_EXPORT void qck_describe(NSString *description, QCKDSLEmptyBlock closure); 59 | QUICK_EXPORT void qck_context(NSString *description, QCKDSLEmptyBlock closure); 60 | QUICK_EXPORT void qck_beforeEach(QCKDSLEmptyBlock closure); 61 | QUICK_EXPORT void qck_beforeEachWithMetadata(QCKDSLExampleMetadataBlock closure); 62 | QUICK_EXPORT void qck_afterEach(QCKDSLEmptyBlock closure); 63 | QUICK_EXPORT void qck_afterEachWithMetadata(QCKDSLExampleMetadataBlock closure); 64 | QUICK_EXPORT void qck_pending(NSString *description, QCKDSLEmptyBlock closure); 65 | QUICK_EXPORT void qck_xdescribe(NSString *description, QCKDSLEmptyBlock closure); 66 | QUICK_EXPORT void qck_xcontext(NSString *description, QCKDSLEmptyBlock closure); 67 | QUICK_EXPORT void qck_fdescribe(NSString *description, QCKDSLEmptyBlock closure); 68 | QUICK_EXPORT void qck_fcontext(NSString *description, QCKDSLEmptyBlock closure); 69 | 70 | #ifndef QUICK_DISABLE_SHORT_SYNTAX 71 | /** 72 | Defines a closure to be run prior to any examples in the test suite. 73 | You may define an unlimited number of these closures, but there is no 74 | guarantee as to the order in which they're run. 75 | 76 | If the test suite crashes before the first example is run, this closure 77 | will not be executed. 78 | 79 | @param closure The closure to be run prior to any examples in the test suite. 80 | */ 81 | static inline void beforeSuite(QCKDSLEmptyBlock closure) { 82 | qck_beforeSuite(closure); 83 | } 84 | 85 | 86 | /** 87 | Defines a closure to be run after all of the examples in the test suite. 88 | You may define an unlimited number of these closures, but there is no 89 | guarantee as to the order in which they're run. 90 | 91 | If the test suite crashes before all examples are run, this closure 92 | will not be executed. 93 | 94 | @param closure The closure to be run after all of the examples in the test suite. 95 | */ 96 | static inline void afterSuite(QCKDSLEmptyBlock closure) { 97 | qck_afterSuite(closure); 98 | } 99 | 100 | /** 101 | Defines a group of shared examples. These examples can be re-used in several locations 102 | by using the `itBehavesLike` function. 103 | 104 | @param name The name of the shared example group. This must be unique across all shared example 105 | groups defined in a test suite. 106 | @param closure A closure containing the examples. This behaves just like an example group defined 107 | using `describe` or `context`--the closure may contain any number of `beforeEach` 108 | and `afterEach` closures, as well as any number of examples (defined using `it`). 109 | */ 110 | static inline void sharedExamples(NSString *name, QCKDSLSharedExampleBlock closure) { 111 | qck_sharedExamples(name, closure); 112 | } 113 | 114 | /** 115 | Defines an example group. Example groups are logical groupings of examples. 116 | Example groups can share setup and teardown code. 117 | 118 | @param description An arbitrary string describing the example group. 119 | @param closure A closure that can contain other examples. 120 | */ 121 | static inline void describe(NSString *description, QCKDSLEmptyBlock closure) { 122 | qck_describe(description, closure); 123 | } 124 | 125 | /** 126 | Defines an example group. Equivalent to `describe`. 127 | */ 128 | static inline void context(NSString *description, QCKDSLEmptyBlock closure) { 129 | qck_context(description, closure); 130 | } 131 | 132 | /** 133 | Defines a closure to be run prior to each example in the current example 134 | group. This closure is not run for pending or otherwise disabled examples. 135 | An example group may contain an unlimited number of beforeEach. They'll be 136 | run in the order they're defined, but you shouldn't rely on that behavior. 137 | 138 | @param closure The closure to be run prior to each example. 139 | */ 140 | static inline void beforeEach(QCKDSLEmptyBlock closure) { 141 | qck_beforeEach(closure); 142 | } 143 | 144 | /** 145 | Identical to QCKDSL.beforeEach, except the closure is provided with 146 | metadata on the example that the closure is being run prior to. 147 | */ 148 | static inline void beforeEachWithMetadata(QCKDSLExampleMetadataBlock closure) { 149 | qck_beforeEachWithMetadata(closure); 150 | } 151 | 152 | /** 153 | Defines a closure to be run after each example in the current example 154 | group. This closure is not run for pending or otherwise disabled examples. 155 | An example group may contain an unlimited number of afterEach. They'll be 156 | run in the order they're defined, but you shouldn't rely on that behavior. 157 | 158 | @param closure The closure to be run after each example. 159 | */ 160 | static inline void afterEach(QCKDSLEmptyBlock closure) { 161 | qck_afterEach(closure); 162 | } 163 | 164 | /** 165 | Identical to QCKDSL.afterEach, except the closure is provided with 166 | metadata on the example that the closure is being run after. 167 | */ 168 | static inline void afterEachWithMetadata(QCKDSLExampleMetadataBlock closure) { 169 | qck_afterEachWithMetadata(closure); 170 | } 171 | 172 | /** 173 | Defines an example or example group that should not be executed. Use `pending` to temporarily disable 174 | examples or groups that should not be run yet. 175 | 176 | @param description An arbitrary string describing the example or example group. 177 | @param closure A closure that will not be evaluated. 178 | */ 179 | static inline void pending(NSString *description, QCKDSLEmptyBlock closure) { 180 | qck_pending(description, closure); 181 | } 182 | 183 | /** 184 | Use this to quickly mark a `describe` block as pending. 185 | This disables all examples within the block. 186 | */ 187 | static inline void xdescribe(NSString *description, QCKDSLEmptyBlock closure) { 188 | qck_xdescribe(description, closure); 189 | } 190 | 191 | /** 192 | Use this to quickly mark a `context` block as pending. 193 | This disables all examples within the block. 194 | */ 195 | static inline void xcontext(NSString *description, QCKDSLEmptyBlock closure) { 196 | qck_xcontext(description, closure); 197 | } 198 | 199 | /** 200 | Use this to quickly focus a `describe` block, focusing the examples in the block. 201 | If any examples in the test suite are focused, only those examples are executed. 202 | This trumps any explicitly focused or unfocused examples within the block--they are all treated as focused. 203 | */ 204 | static inline void fdescribe(NSString *description, QCKDSLEmptyBlock closure) { 205 | qck_fdescribe(description, closure); 206 | } 207 | 208 | /** 209 | Use this to quickly focus a `context` block. Equivalent to `fdescribe`. 210 | */ 211 | static inline void fcontext(NSString *description, QCKDSLEmptyBlock closure) { 212 | qck_fcontext(description, closure); 213 | } 214 | 215 | #define it qck_it 216 | #define xit qck_xit 217 | #define fit qck_fit 218 | #define itBehavesLike qck_itBehavesLike 219 | #define xitBehavesLike qck_xitBehavesLike 220 | #define fitBehavesLike qck_fitBehavesLike 221 | #endif 222 | 223 | #define qck_it qck_it_builder(@{}, @(__FILE__), __LINE__) 224 | #define qck_xit qck_it_builder(@{Filter.pending: @YES}, @(__FILE__), __LINE__) 225 | #define qck_fit qck_it_builder(@{Filter.focused: @YES}, @(__FILE__), __LINE__) 226 | #define qck_itBehavesLike qck_itBehavesLike_builder(@{}, @(__FILE__), __LINE__) 227 | #define qck_xitBehavesLike qck_itBehavesLike_builder(@{Filter.pending: @YES}, @(__FILE__), __LINE__) 228 | #define qck_fitBehavesLike qck_itBehavesLike_builder(@{Filter.focused: @YES}, @(__FILE__), __LINE__) 229 | 230 | typedef void (^QCKItBlock)(NSString *description, QCKDSLEmptyBlock closure); 231 | typedef void (^QCKItBehavesLikeBlock)(NSString *description, QCKDSLSharedExampleContext context); 232 | 233 | QUICK_EXPORT QCKItBlock qck_it_builder(NSDictionary *flags, NSString *file, NSUInteger line); 234 | QUICK_EXPORT QCKItBehavesLikeBlock qck_itBehavesLike_builder(NSDictionary *flags, NSString *file, NSUInteger line); 235 | -------------------------------------------------------------------------------- /Quick/DSL/QCKDSL.m: -------------------------------------------------------------------------------- 1 | #import "QCKDSL.h" 2 | #import "World.h" 3 | #import "World+DSL.h" 4 | 5 | void qck_beforeSuite(QCKDSLEmptyBlock closure) { 6 | [[World sharedWorld] beforeSuite:closure]; 7 | } 8 | 9 | void qck_afterSuite(QCKDSLEmptyBlock closure) { 10 | [[World sharedWorld] afterSuite:closure]; 11 | } 12 | 13 | void qck_sharedExamples(NSString *name, QCKDSLSharedExampleBlock closure) { 14 | [[World sharedWorld] sharedExamples:name closure:closure]; 15 | } 16 | 17 | void qck_describe(NSString *description, QCKDSLEmptyBlock closure) { 18 | [[World sharedWorld] describe:description flags:@{} closure:closure]; 19 | } 20 | 21 | void qck_context(NSString *description, QCKDSLEmptyBlock closure) { 22 | qck_describe(description, closure); 23 | } 24 | 25 | void qck_beforeEach(QCKDSLEmptyBlock closure) { 26 | [[World sharedWorld] beforeEach:closure]; 27 | } 28 | 29 | void qck_beforeEachWithMetadata(QCKDSLExampleMetadataBlock closure) { 30 | [[World sharedWorld] beforeEachWithMetadata:closure]; 31 | } 32 | 33 | void qck_afterEach(QCKDSLEmptyBlock closure) { 34 | [[World sharedWorld] afterEach:closure]; 35 | } 36 | 37 | void qck_afterEachWithMetadata(QCKDSLExampleMetadataBlock closure) { 38 | [[World sharedWorld] afterEachWithMetadata:closure]; 39 | } 40 | 41 | QCKItBlock qck_it_builder(NSDictionary *flags, NSString *file, NSUInteger line) { 42 | return ^(NSString *description, QCKDSLEmptyBlock closure) { 43 | [[World sharedWorld] itWithDescription:description 44 | flags:flags 45 | file:file 46 | line:line 47 | closure:closure]; 48 | }; 49 | } 50 | 51 | QCKItBehavesLikeBlock qck_itBehavesLike_builder(NSDictionary *flags, NSString *file, NSUInteger line) { 52 | return ^(NSString *name, QCKDSLSharedExampleContext context) { 53 | [[World sharedWorld] itBehavesLikeSharedExampleNamed:name 54 | sharedExampleContext:context 55 | flags:flags 56 | file:file 57 | line:line]; 58 | }; 59 | } 60 | 61 | void qck_pending(NSString *description, QCKDSLEmptyBlock closure) { 62 | [[World sharedWorld] pending:description closure:closure]; 63 | } 64 | 65 | void qck_xdescribe(NSString *description, QCKDSLEmptyBlock closure) { 66 | [[World sharedWorld] xdescribe:description flags:@{} closure:closure]; 67 | } 68 | 69 | void qck_xcontext(NSString *description, QCKDSLEmptyBlock closure) { 70 | qck_xdescribe(description, closure); 71 | } 72 | 73 | void qck_fdescribe(NSString *description, QCKDSLEmptyBlock closure) { 74 | [[World sharedWorld] fdescribe:description flags:@{} closure:closure]; 75 | } 76 | 77 | void qck_fcontext(NSString *description, QCKDSLEmptyBlock closure) { 78 | qck_fdescribe(description, closure); 79 | } 80 | -------------------------------------------------------------------------------- /Quick/DSL/World+DSL.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface World (SWIFT_EXTENSION(Quick)) 4 | - (void)beforeSuite:(void (^ __nonnull)(void))closure; 5 | - (void)afterSuite:(void (^ __nonnull)(void))closure; 6 | - (void)sharedExamples:(NSString * __nonnull)name closure:(void (^ __nonnull)(NSDictionary * __nonnull (^ __nonnull)(void)))closure; 7 | - (void)describe:(NSString * __nonnull)description flags:(NSDictionary * __nonnull)flags closure:(void (^ __nonnull)(void))closure; 8 | - (void)context:(NSString * __nonnull)description flags:(NSDictionary * __nonnull)flags closure:(void (^ __nonnull)(void))closure; 9 | - (void)fdescribe:(NSString * __nonnull)description flags:(NSDictionary * __nonnull)flags closure:(void (^ __nonnull)(void))closure; 10 | - (void)xdescribe:(NSString * __nonnull)description flags:(NSDictionary * __nonnull)flags closure:(void (^ __nonnull)(void))closure; 11 | - (void)beforeEach:(void (^ __nonnull)(void))closure; 12 | - (void)beforeEachWithMetadata:(void (^ __nonnull)(ExampleMetadata * __nonnull))closure; 13 | - (void)afterEach:(void (^ __nonnull)(void))closure; 14 | - (void)afterEachWithMetadata:(void (^ __nonnull)(ExampleMetadata * __nonnull))closure; 15 | - (void)itWithDescription:(NSString * __nonnull)description flags:(NSDictionary * __nonnull)flags file:(NSString * __nonnull)file line:(NSUInteger)line closure:(void (^ __nonnull)(void))closure; 16 | - (void)fitWithDescription:(NSString * __nonnull)description flags:(NSDictionary * __nonnull)flags file:(NSString * __nonnull)file line:(NSUInteger)line closure:(void (^ __nonnull)(void))closure; 17 | - (void)xitWithDescription:(NSString * __nonnull)description flags:(NSDictionary * __nonnull)flags file:(NSString * __nonnull)file line:(NSUInteger)line closure:(void (^ __nonnull)(void))closure; 18 | - (void)itBehavesLikeSharedExampleNamed:(NSString * __nonnull)name sharedExampleContext:(NSDictionary * __nonnull (^ __nonnull)(void))sharedExampleContext flags:(NSDictionary * __nonnull)flags file:(NSString * __nonnull)file line:(NSUInteger)line; 19 | - (void)pending:(NSString * __nonnull)description closure:(void (^ __nonnull)(void))closure; 20 | @end 21 | -------------------------------------------------------------------------------- /Quick/DSL/World+DSL.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Adds methods to World to support top-level DSL functions (Swift) and 3 | macros (Objective-C). These functions map directly to the DSL that test 4 | writers use in their specs. 5 | */ 6 | extension World { 7 | internal func beforeSuite(closure: BeforeSuiteClosure) { 8 | suiteHooks.appendBefore(closure) 9 | } 10 | 11 | internal func afterSuite(closure: AfterSuiteClosure) { 12 | suiteHooks.appendAfter(closure) 13 | } 14 | 15 | internal func sharedExamples(name: String, closure: SharedExampleClosure) { 16 | registerSharedExample(name, closure: closure) 17 | } 18 | 19 | internal func describe(description: String, flags: FilterFlags, closure: () -> ()) { 20 | let group = ExampleGroup(description: description, flags: flags) 21 | currentExampleGroup!.appendExampleGroup(group) 22 | currentExampleGroup = group 23 | closure() 24 | currentExampleGroup = group.parent 25 | } 26 | 27 | internal func context(description: String, flags: FilterFlags, closure: () -> ()) { 28 | self.describe(description, flags: flags, closure: closure) 29 | } 30 | 31 | internal func fdescribe(description: String, flags: FilterFlags, closure: () -> ()) { 32 | var focusedFlags = flags 33 | focusedFlags[Filter.focused] = true 34 | self.describe(description, flags: focusedFlags, closure: closure) 35 | } 36 | 37 | internal func xdescribe(description: String, flags: FilterFlags, closure: () -> ()) { 38 | var pendingFlags = flags 39 | pendingFlags[Filter.pending] = true 40 | self.describe(description, flags: pendingFlags, closure: closure) 41 | } 42 | 43 | internal func beforeEach(closure: BeforeExampleClosure) { 44 | currentExampleGroup!.hooks.appendBefore(closure) 45 | } 46 | 47 | @objc(beforeEachWithMetadata:) 48 | internal func beforeEach(closure closure: BeforeExampleWithMetadataClosure) { 49 | currentExampleGroup!.hooks.appendBefore(closure) 50 | } 51 | 52 | internal func afterEach(closure: AfterExampleClosure) { 53 | currentExampleGroup!.hooks.appendAfter(closure) 54 | } 55 | 56 | @objc(afterEachWithMetadata:) 57 | internal func afterEach(closure closure: AfterExampleWithMetadataClosure) { 58 | currentExampleGroup!.hooks.appendAfter(closure) 59 | } 60 | 61 | @objc(itWithDescription:flags:file:line:closure:) 62 | internal func it(description: String, flags: FilterFlags, file: String, line: UInt, closure: () -> ()) { 63 | let callsite = Callsite(file: file, line: line) 64 | let example = Example(description: description, callsite: callsite, flags: flags, closure: closure) 65 | currentExampleGroup!.appendExample(example) 66 | } 67 | 68 | @objc(fitWithDescription:flags:file:line:closure:) 69 | internal func fit(description: String, flags: FilterFlags, file: String, line: UInt, closure: () -> ()) { 70 | var focusedFlags = flags 71 | focusedFlags[Filter.focused] = true 72 | self.it(description, flags: focusedFlags, file: file, line: line, closure: closure) 73 | } 74 | 75 | @objc(xitWithDescription:flags:file:line:closure:) 76 | internal func xit(description: String, flags: FilterFlags, file: String, line: UInt, closure: () -> ()) { 77 | var pendingFlags = flags 78 | pendingFlags[Filter.pending] = true 79 | self.it(description, flags: pendingFlags, file: file, line: line, closure: closure) 80 | } 81 | 82 | @objc(itBehavesLikeSharedExampleNamed:sharedExampleContext:flags:file:line:) 83 | internal func itBehavesLike(name: String, sharedExampleContext: SharedExampleContext, flags: FilterFlags, file: String, line: UInt) { 84 | let callsite = Callsite(file: file, line: line) 85 | let closure = World.sharedWorld().sharedExample(name) 86 | 87 | let group = ExampleGroup(description: name, flags: flags) 88 | currentExampleGroup!.appendExampleGroup(group) 89 | currentExampleGroup = group 90 | closure(sharedExampleContext) 91 | currentExampleGroup!.walkDownExamples { (example: Example) in 92 | example.isSharedExample = true 93 | example.callsite = callsite 94 | } 95 | 96 | currentExampleGroup = group.parent 97 | } 98 | 99 | internal func pending(description: String, closure: () -> ()) { 100 | print("Pending: \(description)") 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Quick/Example.swift: -------------------------------------------------------------------------------- 1 | private var numberOfExamplesRun = 0 2 | 3 | /** 4 | Examples, defined with the `it` function, use assertions to 5 | demonstrate how code should behave. These are like "tests" in XCTest. 6 | */ 7 | final public class Example: NSObject { 8 | /** 9 | A boolean indicating whether the example is a shared example; 10 | i.e.: whether it is an example defined with `itBehavesLike`. 11 | */ 12 | public var isSharedExample = false 13 | 14 | /** 15 | The site at which the example is defined. 16 | This must be set correctly in order for Xcode to highlight 17 | the correct line in red when reporting a failure. 18 | */ 19 | public var callsite: Callsite 20 | 21 | weak internal var group: ExampleGroup? 22 | 23 | private let internalDescription: String 24 | private let closure: () -> () 25 | private let flags: FilterFlags 26 | 27 | internal init(description: String, callsite: Callsite, flags: FilterFlags, closure: () -> ()) { 28 | self.internalDescription = description 29 | self.closure = closure 30 | self.callsite = callsite 31 | self.flags = flags 32 | } 33 | 34 | public override var description: String { 35 | return internalDescription 36 | } 37 | 38 | /** 39 | The example name. A name is a concatenation of the name of 40 | the example group the example belongs to, followed by the 41 | description of the example itself. 42 | 43 | The example name is used to generate a test method selector 44 | to be displayed in Xcode's test navigator. 45 | */ 46 | public var name: String { 47 | switch group!.name { 48 | case .Some(let groupName): return "\(groupName), \(description)" 49 | case .None: return description 50 | } 51 | } 52 | 53 | /** 54 | Executes the example closure, as well as all before and after 55 | closures defined in the its surrounding example groups. 56 | */ 57 | public func run() { 58 | let world = World.sharedWorld() 59 | 60 | if numberOfExamplesRun == 0 { 61 | world.suiteHooks.executeBefores() 62 | } 63 | 64 | let exampleMetadata = ExampleMetadata(example: self, exampleIndex: numberOfExamplesRun) 65 | world.currentExampleMetadata = exampleMetadata 66 | 67 | world.exampleHooks.executeBefores(exampleMetadata) 68 | for before in group!.befores { 69 | before(exampleMetadata: exampleMetadata) 70 | } 71 | 72 | closure() 73 | 74 | for after in group!.afters { 75 | after(exampleMetadata: exampleMetadata) 76 | } 77 | world.exampleHooks.executeAfters(exampleMetadata) 78 | 79 | ++numberOfExamplesRun 80 | 81 | if !world.isRunningAdditionalSuites && numberOfExamplesRun >= world.exampleCount { 82 | world.suiteHooks.executeAfters() 83 | } 84 | } 85 | 86 | /** 87 | Evaluates the filter flags set on this example and on the example groups 88 | this example belongs to. Flags set on the example are trumped by flags on 89 | the example group it belongs to. Flags on inner example groups are trumped 90 | by flags on outer example groups. 91 | */ 92 | internal var filterFlags: FilterFlags { 93 | var aggregateFlags = flags 94 | for (key, value) in group!.filterFlags { 95 | aggregateFlags[key] = value 96 | } 97 | return aggregateFlags 98 | } 99 | } 100 | 101 | /** 102 | Returns a boolean indicating whether two Example objects are equal. 103 | If two examples are defined at the exact same callsite, they must be equal. 104 | */ 105 | public func ==(lhs: Example, rhs: Example) -> Bool { 106 | return lhs.callsite == rhs.callsite 107 | } 108 | -------------------------------------------------------------------------------- /Quick/ExampleGroup.swift: -------------------------------------------------------------------------------- 1 | /** 2 | Example groups are logical groupings of examples, defined with 3 | the `describe` and `context` functions. Example groups can share 4 | setup and teardown code. 5 | */ 6 | final public class ExampleGroup: NSObject { 7 | weak internal var parent: ExampleGroup? 8 | internal let hooks = ExampleHooks() 9 | 10 | private let internalDescription: String 11 | private let flags: FilterFlags 12 | private let isInternalRootExampleGroup: Bool 13 | private var childGroups = [ExampleGroup]() 14 | private var childExamples = [Example]() 15 | 16 | internal init(description: String, flags: FilterFlags, isInternalRootExampleGroup: Bool = false) { 17 | self.internalDescription = description 18 | self.flags = flags 19 | self.isInternalRootExampleGroup = isInternalRootExampleGroup 20 | } 21 | 22 | public override var description: String { 23 | return internalDescription 24 | } 25 | 26 | /** 27 | Returns a list of examples that belong to this example group, 28 | or to any of its descendant example groups. 29 | */ 30 | public var examples: [Example] { 31 | var examples = childExamples 32 | for group in childGroups { 33 | examples.appendContentsOf(group.examples) 34 | } 35 | return examples 36 | } 37 | 38 | internal var name: String? { 39 | if let parent = parent { 40 | switch(parent.name) { 41 | case .Some(let name): return "\(name), \(description)" 42 | case .None: return description 43 | } 44 | } else { 45 | return isInternalRootExampleGroup ? nil : description 46 | } 47 | } 48 | 49 | internal var filterFlags: FilterFlags { 50 | var aggregateFlags = flags 51 | walkUp() { (group: ExampleGroup) -> () in 52 | for (key, value) in group.flags { 53 | aggregateFlags[key] = value 54 | } 55 | } 56 | return aggregateFlags 57 | } 58 | 59 | internal var befores: [BeforeExampleWithMetadataClosure] { 60 | var closures = Array(hooks.befores.reverse()) 61 | walkUp() { (group: ExampleGroup) -> () in 62 | closures.appendContentsOf(Array(group.hooks.befores.reverse())) 63 | } 64 | return Array(closures.reverse()) 65 | } 66 | 67 | internal var afters: [AfterExampleWithMetadataClosure] { 68 | var closures = hooks.afters 69 | walkUp() { (group: ExampleGroup) -> () in 70 | closures.appendContentsOf(group.hooks.afters) 71 | } 72 | return closures 73 | } 74 | 75 | internal func walkDownExamples(callback: (example: Example) -> ()) { 76 | for example in childExamples { 77 | callback(example: example) 78 | } 79 | for group in childGroups { 80 | group.walkDownExamples(callback) 81 | } 82 | } 83 | 84 | internal func appendExampleGroup(group: ExampleGroup) { 85 | group.parent = self 86 | childGroups.append(group) 87 | } 88 | 89 | internal func appendExample(example: Example) { 90 | example.group = self 91 | childExamples.append(example) 92 | } 93 | 94 | private func walkUp(callback: (group: ExampleGroup) -> ()) { 95 | var group = self 96 | while let parent = group.parent { 97 | callback(group: parent) 98 | group = parent 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Quick/ExampleMetadata.swift: -------------------------------------------------------------------------------- 1 | /** 2 | A class that encapsulates information about an example, 3 | including the index at which the example was executed, as 4 | well as the example itself. 5 | */ 6 | final public class ExampleMetadata: NSObject { 7 | /** 8 | The example for which this metadata was collected. 9 | */ 10 | public let example: Example 11 | 12 | /** 13 | The index at which this example was executed in the 14 | test suite. 15 | */ 16 | public let exampleIndex: Int 17 | 18 | internal init(example: Example, exampleIndex: Int) { 19 | self.example = example 20 | self.exampleIndex = exampleIndex 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Quick/Filter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** 4 | A mapping of string keys to booleans that can be used to 5 | filter examples or example groups. For example, a "focused" 6 | example would have the flags [Focused: true]. 7 | */ 8 | public typealias FilterFlags = [String: Bool] 9 | 10 | /** 11 | A namespace for filter flag keys, defined primarily to make the 12 | keys available in Objective-C. 13 | */ 14 | final public class Filter: NSObject { 15 | /** 16 | Example and example groups with [Focused: true] are included in test runs, 17 | excluding all other examples without this flag. Use this to only run one or 18 | two tests that you're currently focusing on. 19 | */ 20 | public class var focused: String { 21 | return "focused" 22 | } 23 | 24 | /** 25 | Example and example groups with [Pending: true] are excluded from test runs. 26 | Use this to temporarily suspend examples that you know do not pass yet. 27 | */ 28 | public class var pending: String { 29 | return "pending" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Quick/Hooks/Closures.swift: -------------------------------------------------------------------------------- 1 | // MARK: Example Hooks 2 | 3 | /** 4 | A closure executed before an example is run. 5 | */ 6 | public typealias BeforeExampleClosure = () -> () 7 | 8 | /** 9 | A closure executed before an example is run. The closure is given example metadata, 10 | which contains information about the example that is about to be run. 11 | */ 12 | public typealias BeforeExampleWithMetadataClosure = (exampleMetadata: ExampleMetadata) -> () 13 | 14 | /** 15 | A closure executed after an example is run. 16 | */ 17 | public typealias AfterExampleClosure = BeforeExampleClosure 18 | 19 | /** 20 | A closure executed after an example is run. The closure is given example metadata, 21 | which contains information about the example that has just finished running. 22 | */ 23 | public typealias AfterExampleWithMetadataClosure = BeforeExampleWithMetadataClosure 24 | 25 | // MARK: Suite Hooks 26 | 27 | /** 28 | A closure executed before any examples are run. 29 | */ 30 | public typealias BeforeSuiteClosure = () -> () 31 | 32 | /** 33 | A closure executed after all examples have finished running. 34 | */ 35 | public typealias AfterSuiteClosure = BeforeSuiteClosure 36 | -------------------------------------------------------------------------------- /Quick/Hooks/ExampleHooks.swift: -------------------------------------------------------------------------------- 1 | /** 2 | A container for closures to be executed before and after each example. 3 | */ 4 | final internal class ExampleHooks { 5 | 6 | internal var befores: [BeforeExampleWithMetadataClosure] = [] 7 | internal var afters: [AfterExampleWithMetadataClosure] = [] 8 | 9 | internal func appendBefore(closure: BeforeExampleWithMetadataClosure) { 10 | befores.append(closure) 11 | } 12 | 13 | internal func appendBefore(closure: BeforeExampleClosure) { 14 | befores.append { (exampleMetadata: ExampleMetadata) in closure() } 15 | } 16 | 17 | internal func appendAfter(closure: AfterExampleWithMetadataClosure) { 18 | afters.append(closure) 19 | } 20 | 21 | internal func appendAfter(closure: AfterExampleClosure) { 22 | afters.append { (exampleMetadata: ExampleMetadata) in closure() } 23 | } 24 | 25 | internal func executeBefores(exampleMetadata: ExampleMetadata) { 26 | for before in befores { 27 | before(exampleMetadata: exampleMetadata) 28 | } 29 | } 30 | 31 | internal func executeAfters(exampleMetadata: ExampleMetadata) { 32 | for after in afters { 33 | after(exampleMetadata: exampleMetadata) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Quick/Hooks/SuiteHooks.swift: -------------------------------------------------------------------------------- 1 | /** 2 | A container for closures to be executed before and after all examples. 3 | */ 4 | final internal class SuiteHooks { 5 | internal var befores: [BeforeSuiteClosure] = [] 6 | internal var beforesAlreadyExecuted = false 7 | 8 | internal var afters: [AfterSuiteClosure] = [] 9 | internal var aftersAlreadyExecuted = false 10 | 11 | internal func appendBefore(closure: BeforeSuiteClosure) { 12 | befores.append(closure) 13 | } 14 | 15 | internal func appendAfter(closure: AfterSuiteClosure) { 16 | afters.append(closure) 17 | } 18 | 19 | internal func executeBefores() { 20 | assert(!beforesAlreadyExecuted) 21 | for before in befores { 22 | before() 23 | } 24 | beforesAlreadyExecuted = true 25 | } 26 | 27 | internal func executeAfters() { 28 | assert(!aftersAlreadyExecuted) 29 | for after in afters { 30 | after() 31 | } 32 | aftersAlreadyExecuted = true 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Quick/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSHumanReadableCopyright 24 | Copyright © 2014 - present, Quick Team. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Quick/NSString+QCKSelectorName.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | /** 4 | QuickSpec converts example names into test methods. 5 | Those test methods need valid selector names, which means no whitespace, 6 | control characters, etc. This category gives NSString objects an easy way 7 | to replace those illegal characters with underscores. 8 | */ 9 | @interface NSString (QCKSelectorName) 10 | 11 | /** 12 | Returns a string with underscores in place of all characters that cannot 13 | be included in a selector (SEL) name. 14 | */ 15 | @property (nonatomic, readonly) NSString *qck_selectorName; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Quick/NSString+QCKSelectorName.m: -------------------------------------------------------------------------------- 1 | #import "NSString+QCKSelectorName.h" 2 | 3 | @implementation NSString (QCKSelectorName) 4 | 5 | - (NSString *)qck_selectorName { 6 | static NSMutableCharacterSet *invalidCharacters = nil; 7 | static dispatch_once_t onceToken; 8 | dispatch_once(&onceToken, ^{ 9 | invalidCharacters = [NSMutableCharacterSet new]; 10 | 11 | NSCharacterSet *whitespaceCharacterSet = [NSCharacterSet whitespaceCharacterSet]; 12 | NSCharacterSet *newlineCharacterSet = [NSCharacterSet newlineCharacterSet]; 13 | NSCharacterSet *illegalCharacterSet = [NSCharacterSet illegalCharacterSet]; 14 | NSCharacterSet *controlCharacterSet = [NSCharacterSet controlCharacterSet]; 15 | NSCharacterSet *punctuationCharacterSet = [NSCharacterSet punctuationCharacterSet]; 16 | NSCharacterSet *nonBaseCharacterSet = [NSCharacterSet nonBaseCharacterSet]; 17 | NSCharacterSet *symbolCharacterSet = [NSCharacterSet symbolCharacterSet]; 18 | 19 | [invalidCharacters formUnionWithCharacterSet:whitespaceCharacterSet]; 20 | [invalidCharacters formUnionWithCharacterSet:newlineCharacterSet]; 21 | [invalidCharacters formUnionWithCharacterSet:illegalCharacterSet]; 22 | [invalidCharacters formUnionWithCharacterSet:controlCharacterSet]; 23 | [invalidCharacters formUnionWithCharacterSet:punctuationCharacterSet]; 24 | [invalidCharacters formUnionWithCharacterSet:nonBaseCharacterSet]; 25 | [invalidCharacters formUnionWithCharacterSet:symbolCharacterSet]; 26 | }); 27 | 28 | NSArray *validComponents = [self componentsSeparatedByCharactersInSet:invalidCharacters]; 29 | 30 | return [validComponents componentsJoinedByString:@"_"]; 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /Quick/Quick.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | //! Project version number for Quick. 4 | FOUNDATION_EXPORT double QuickVersionNumber; 5 | 6 | //! Project version string for Quick. 7 | FOUNDATION_EXPORT const unsigned char QuickVersionString[]; 8 | 9 | // In this header, you should import all the public headers of your framework using statements like #import 10 | 11 | #import 12 | #import 13 | #import 14 | -------------------------------------------------------------------------------- /Quick/QuickSpec.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | /** 4 | QuickSpec is a base class all specs written in Quick inherit from. 5 | They need to inherit from QuickSpec, a subclass of XCTestCase, in 6 | order to be discovered by the XCTest framework. 7 | 8 | XCTest automatically compiles a list of XCTestCase subclasses included 9 | in the test target. It iterates over each class in that list, and creates 10 | a new instance of that class for each test method. It then creates an 11 | "invocation" to execute that test method. The invocation is an instance of 12 | NSInvocation, which represents a single message send in Objective-C. 13 | The invocation is set on the XCTestCase instance, and the test is run. 14 | 15 | Most of the code in QuickSpec is dedicated to hooking into XCTest events. 16 | First, when the spec is first loaded and before it is sent any messages, 17 | the +[NSObject initialize] method is called. QuickSpec overrides this method 18 | to call +[QuickSpec spec]. This builds the example group stacks and 19 | registers them with Quick.World, a global register of examples. 20 | 21 | Then, XCTest queries QuickSpec for a list of test methods. Normally, XCTest 22 | automatically finds all methods whose selectors begin with the string "test". 23 | However, QuickSpec overrides this default behavior by implementing the 24 | +[XCTestCase testInvocations] method. This method iterates over each example 25 | registered in Quick.World, defines a new method for that example, and 26 | returns an invocation to call that method to XCTest. Those invocations are 27 | the tests that are run by XCTest. Their selector names are displayed in 28 | the Xcode test navigation bar. 29 | */ 30 | @interface QuickSpec : XCTestCase 31 | 32 | /** 33 | Override this method in your spec to define a set of example groups 34 | and examples. 35 | 36 | override class func spec() { 37 | describe("winter") { 38 | it("is coming") { 39 | // ... 40 | } 41 | } 42 | } 43 | 44 | See DSL.swift for more information on what syntax is available. 45 | */ 46 | - (void)spec; 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /Quick/QuickSpec.m: -------------------------------------------------------------------------------- 1 | #import "QuickSpec.h" 2 | #import "QuickConfiguration.h" 3 | #import "NSString+QCKSelectorName.h" 4 | #import "World.h" 5 | #import 6 | 7 | static QuickSpec *currentSpec = nil; 8 | 9 | const void * const QCKExampleKey = &QCKExampleKey; 10 | 11 | @interface QuickSpec () 12 | @property (nonatomic, strong) Example *example; 13 | @end 14 | 15 | @implementation QuickSpec 16 | 17 | #pragma mark - XCTestCase Overrides 18 | 19 | /** 20 | The runtime sends initialize to each class in a program just before the class, or any class 21 | that inherits from it, is sent its first message from within the program. QuickSpec hooks into 22 | this event to compile the example groups for this spec subclass. 23 | 24 | If an exception occurs when compiling the examples, report it to the user. Chances are they 25 | included an expectation outside of a "it", "describe", or "context" block. 26 | */ 27 | + (void)initialize { 28 | [QuickConfiguration initialize]; 29 | 30 | World *world = [World sharedWorld]; 31 | world.currentExampleGroup = [world rootExampleGroupForSpecClass:[self class]]; 32 | QuickSpec *spec = [self new]; 33 | 34 | @try { 35 | [spec spec]; 36 | } 37 | @catch (NSException *exception) { 38 | [NSException raise:NSInternalInconsistencyException 39 | format:@"An exception occurred when building Quick's example groups.\n" 40 | @"Some possible reasons this might happen include:\n\n" 41 | @"- An 'expect(...).to' expectation was evaluated outside of " 42 | @"an 'it', 'context', or 'describe' block\n" 43 | @"- 'sharedExamples' was called twice with the same name\n" 44 | @"- 'itBehavesLike' was called with a name that is not registered as a shared example\n\n" 45 | @"Here's the original exception: '%@', reason: '%@', userInfo: '%@'", 46 | exception.name, exception.reason, exception.userInfo]; 47 | } 48 | [self testInvocations]; 49 | } 50 | 51 | /** 52 | Invocations for each test method in the test case. QuickSpec overrides this method to define a 53 | new method for each example defined in +[QuickSpec spec]. 54 | 55 | @return An array of invocations that execute the newly defined example methods. 56 | */ 57 | + (NSArray *)testInvocations { 58 | NSArray *examples = [[World sharedWorld] examplesForSpecClass:[self class]]; 59 | NSMutableArray *invocations = [NSMutableArray arrayWithCapacity:[examples count]]; 60 | for (Example *example in examples) { 61 | SEL selector = [self addInstanceMethodForExample:example]; 62 | NSInvocation *invocation = [self invocationForInstanceMethodWithSelector:selector 63 | example:example]; 64 | [invocations addObject:invocation]; 65 | } 66 | 67 | return invocations; 68 | } 69 | 70 | /** 71 | XCTest sets the invocation for the current test case instance using this setter. 72 | QuickSpec hooks into this event to give the test case a reference to the current example. 73 | It will need this reference to correctly report its name to XCTest. 74 | */ 75 | - (void)setInvocation:(NSInvocation *)invocation { 76 | self.example = objc_getAssociatedObject(invocation, QCKExampleKey); 77 | [super setInvocation:invocation]; 78 | } 79 | 80 | #pragma mark - Public Interface 81 | 82 | - (void)spec { } 83 | 84 | #pragma mark - Internal Methods 85 | 86 | /** 87 | QuickSpec uses this method to dynamically define a new instance method for the 88 | given example. The instance method runs the example, catching any exceptions. 89 | The exceptions are then reported as test failures. 90 | 91 | In order to report the correct file and line number, examples must raise exceptions 92 | containing following keys in their userInfo: 93 | 94 | - "SenTestFilenameKey": A String representing the file name 95 | - "SenTestLineNumberKey": An Int representing the line number 96 | 97 | These keys used to be used by SenTestingKit, and are still used by some testing tools 98 | in the wild. See: https://github.com/Quick/Quick/pull/41 99 | 100 | @return The selector of the newly defined instance method. 101 | */ 102 | + (SEL)addInstanceMethodForExample:(Example *)example { 103 | IMP implementation = imp_implementationWithBlock(^(QuickSpec *self){ 104 | currentSpec = self; 105 | [example run]; 106 | }); 107 | NSCharacterSet *characterSet = [NSCharacterSet alphanumericCharacterSet]; 108 | NSMutableString *sanitizedFileName = [NSMutableString string]; 109 | for (NSUInteger i = 0; i < example.callsite.file.length; i++) { 110 | unichar ch = [example.callsite.file characterAtIndex:i]; 111 | if ([characterSet characterIsMember:ch]) { 112 | [sanitizedFileName appendFormat:@"%c", ch]; 113 | } 114 | } 115 | 116 | const char *types = [[NSString stringWithFormat:@"%s%s%s", @encode(id), @encode(id), @encode(SEL)] UTF8String]; 117 | NSString *selectorName = [NSString stringWithFormat:@"%@_%@_%ld", 118 | example.name.qck_selectorName, 119 | sanitizedFileName, 120 | (long)example.callsite.line]; 121 | SEL selector = NSSelectorFromString(selectorName); 122 | class_addMethod(self, selector, implementation, types); 123 | 124 | return selector; 125 | } 126 | 127 | + (NSInvocation *)invocationForInstanceMethodWithSelector:(SEL)selector 128 | example:(Example *)example { 129 | NSMethodSignature *signature = [self instanceMethodSignatureForSelector:selector]; 130 | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; 131 | invocation.selector = selector; 132 | objc_setAssociatedObject(invocation, 133 | QCKExampleKey, 134 | example, 135 | OBJC_ASSOCIATION_RETAIN_NONATOMIC); 136 | return invocation; 137 | } 138 | 139 | /** 140 | This method is used to record failures, whether they represent example 141 | expectations that were not met, or exceptions raised during test setup 142 | and teardown. By default, the failure will be reported as an 143 | XCTest failure, and the example will be highlighted in Xcode. 144 | */ 145 | - (void)recordFailureWithDescription:(NSString *)description 146 | inFile:(NSString *)filePath 147 | atLine:(NSUInteger)lineNumber 148 | expected:(BOOL)expected { 149 | if (self.example.isSharedExample) { 150 | filePath = self.example.callsite.file; 151 | lineNumber = self.example.callsite.line; 152 | } 153 | [currentSpec.testRun recordFailureWithDescription:description 154 | inFile:filePath 155 | atLine:lineNumber 156 | expected:expected]; 157 | } 158 | 159 | @end 160 | -------------------------------------------------------------------------------- /Quick/World.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @class ExampleGroup; 4 | @class ExampleMetadata; 5 | 6 | SWIFT_CLASS("_TtC5Quick5World") 7 | @interface World 8 | 9 | @property (nonatomic) ExampleGroup * __nullable currentExampleGroup; 10 | @property (nonatomic) ExampleMetadata * __nullable currentExampleMetadata; 11 | @property (nonatomic) BOOL isRunningAdditionalSuites; 12 | + (World * __nonnull)sharedWorld; 13 | - (void)configure:(void (^ __nonnull)(Configuration * __nonnull))closure; 14 | - (void)finalizeConfiguration; 15 | - (ExampleGroup * __nonnull)rootExampleGroupForSpecClass:(Class __nonnull)cls; 16 | - (NSArray * __nonnull)examplesForSpecClass:(Class __nonnull)specClass; 17 | @end 18 | -------------------------------------------------------------------------------- /Quick/World.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** 4 | A closure that, when evaluated, returns a dictionary of key-value 5 | pairs that can be accessed from within a group of shared examples. 6 | */ 7 | public typealias SharedExampleContext = () -> (NSDictionary) 8 | 9 | /** 10 | A closure that is used to define a group of shared examples. This 11 | closure may contain any number of example and example groups. 12 | */ 13 | public typealias SharedExampleClosure = (SharedExampleContext) -> () 14 | 15 | /** 16 | A collection of state Quick builds up in order to work its magic. 17 | World is primarily responsible for maintaining a mapping of QuickSpec 18 | classes to root example groups for those classes. 19 | 20 | It also maintains a mapping of shared example names to shared 21 | example closures. 22 | 23 | You may configure how Quick behaves by calling the -[World configure:] 24 | method from within an overridden +[QuickConfiguration configure:] method. 25 | */ 26 | final internal class World: NSObject { 27 | /** 28 | The example group that is currently being run. 29 | The DSL requires that this group is correctly set in order to build a 30 | correct hierarchy of example groups and their examples. 31 | */ 32 | internal var currentExampleGroup: ExampleGroup? 33 | 34 | /** 35 | The example metadata of the test that is currently being run. 36 | This is useful for using the Quick test metadata (like its name) at 37 | runtime. 38 | */ 39 | 40 | internal var currentExampleMetadata: ExampleMetadata? 41 | 42 | /** 43 | A flag that indicates whether additional test suites are being run 44 | within this test suite. This is only true within the context of Quick 45 | functional tests. 46 | */ 47 | internal var isRunningAdditionalSuites = false 48 | 49 | private var specs: Dictionary = [:] 50 | private var sharedExamples: [String: SharedExampleClosure] = [:] 51 | private let configuration = Configuration() 52 | private var isConfigurationFinalized = false 53 | 54 | internal var exampleHooks: ExampleHooks {return configuration.exampleHooks } 55 | internal var suiteHooks: SuiteHooks { return configuration.suiteHooks } 56 | 57 | // MARK: Singleton Constructor 58 | 59 | private override init() {} 60 | private struct Shared { 61 | static let instance = World() 62 | } 63 | internal class func sharedWorld() -> World { 64 | return Shared.instance 65 | } 66 | 67 | // MARK: Public Interface 68 | 69 | /** 70 | Exposes the World's Configuration object within the scope of the closure 71 | so that it may be configured. This method must not be called outside of 72 | an overridden +[QuickConfiguration configure:] method. 73 | 74 | - parameter closure: A closure that takes a Configuration object that can 75 | be mutated to change Quick's behavior. 76 | */ 77 | internal func configure(closure: QuickConfigurer) { 78 | assert(!isConfigurationFinalized, 79 | "Quick cannot be configured outside of a +[QuickConfiguration configure:] method. You should not call -[World configure:] directly. Instead, subclass QuickConfiguration and override the +[QuickConfiguration configure:] method.") 80 | closure(configuration: configuration) 81 | } 82 | 83 | /** 84 | Finalizes the World's configuration. 85 | Any subsequent calls to World.configure() will raise. 86 | */ 87 | internal func finalizeConfiguration() { 88 | isConfigurationFinalized = true 89 | } 90 | 91 | /** 92 | Returns an internally constructed root example group for the given 93 | QuickSpec class. 94 | 95 | A root example group with the description "root example group" is lazily 96 | initialized for each QuickSpec class. This root example group wraps the 97 | top level of a -[QuickSpec spec] method--it's thanks to this group that 98 | users can define beforeEach and it closures at the top level, like so: 99 | 100 | override func spec() { 101 | // These belong to the root example group 102 | beforeEach {} 103 | it("is at the top level") {} 104 | } 105 | 106 | - parameter cls: The QuickSpec class for which to retrieve the root example group. 107 | - returns: The root example group for the class. 108 | */ 109 | internal func rootExampleGroupForSpecClass(cls: AnyClass) -> ExampleGroup { 110 | let name = NSStringFromClass(cls) 111 | if let group = specs[name] { 112 | return group 113 | } else { 114 | let group = ExampleGroup( 115 | description: "root example group", 116 | flags: [:], 117 | isInternalRootExampleGroup: true 118 | ) 119 | specs[name] = group 120 | return group 121 | } 122 | } 123 | 124 | /** 125 | Returns all examples that should be run for a given spec class. 126 | There are two filtering passes that occur when determining which examples should be run. 127 | That is, these examples are the ones that are included by inclusion filters, and are 128 | not excluded by exclusion filters. 129 | 130 | - parameter specClass: The QuickSpec subclass for which examples are to be returned. 131 | - returns: A list of examples to be run as test invocations. 132 | */ 133 | @objc(examplesForSpecClass:) 134 | internal func examples(specClass: AnyClass) -> [Example] { 135 | // 1. Grab all included examples. 136 | let included = includedExamples 137 | // 2. Grab the intersection of (a) examples for this spec, and (b) included examples. 138 | let spec = rootExampleGroupForSpecClass(specClass).examples.filter { included.contains($0) } 139 | // 3. Remove all excluded examples. 140 | return spec.filter { example in 141 | !self.configuration.exclusionFilters.reduce(false) { $0 || $1(example: example) } 142 | } 143 | } 144 | 145 | // MARK: Internal 146 | 147 | internal func registerSharedExample(name: String, closure: SharedExampleClosure) { 148 | raiseIfSharedExampleAlreadyRegistered(name) 149 | sharedExamples[name] = closure 150 | } 151 | 152 | internal func sharedExample(name: String) -> SharedExampleClosure { 153 | raiseIfSharedExampleNotRegistered(name) 154 | return sharedExamples[name]! 155 | } 156 | 157 | internal var exampleCount: Int { 158 | return allExamples.count 159 | } 160 | 161 | private var allExamples: [Example] { 162 | var all: [Example] = [] 163 | for (_, group) in specs { 164 | group.walkDownExamples { all.append($0) } 165 | } 166 | return all 167 | } 168 | 169 | private var includedExamples: [Example] { 170 | let all = allExamples 171 | let included = all.filter { example in 172 | return self.configuration.inclusionFilters.reduce(false) { $0 || $1(example: example) } 173 | } 174 | 175 | if included.isEmpty && configuration.runAllWhenEverythingFiltered { 176 | return all 177 | } else { 178 | return included 179 | } 180 | } 181 | 182 | private func raiseIfSharedExampleAlreadyRegistered(name: String) { 183 | if sharedExamples[name] != nil { 184 | NSException(name: NSInternalInconsistencyException, 185 | reason: "A shared example named '\(name)' has already been registered.", 186 | userInfo: nil).raise() 187 | } 188 | } 189 | 190 | private func raiseIfSharedExampleNotRegistered(name: String) { 191 | if sharedExamples[name] == nil { 192 | NSException(name: NSInternalInconsistencyException, 193 | reason: "No shared example named '\(name)' has been registered. Registered shared examples: '\(Array(sharedExamples.keys))'", 194 | userInfo: nil).raise() 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /QuickFocusedTests/FocusedTests+ObjC.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | 5 | #import "QCKSpecRunner.h" 6 | 7 | QuickConfigurationBegin(FunctionalTests_SharedExamplesConfiguration) 8 | 9 | + (void)configure:(Configuration *)configuration { 10 | sharedExamples(@"two passing shared examples (Objective-C)", ^(QCKDSLSharedExampleContext exampleContext) { 11 | it(@"has an example that passes (4)", ^{}); 12 | it(@"has another example that passes (5)", ^{}); 13 | }); 14 | } 15 | 16 | QuickConfigurationEnd 17 | 18 | QuickSpecBegin(FunctionalTests_FocusedSpec_Focused) 19 | 20 | it(@"has an unfocused example that fails, but is never run", ^{ XCTFail(); }); 21 | fit(@"has a focused example that passes (1)", ^{}); 22 | 23 | fdescribe(@"a focused example group", ^{ 24 | it(@"has an example that is not focused, but will be run, and passes (2)", ^{}); 25 | fit(@"has a focused example that passes (3)", ^{}); 26 | }); 27 | 28 | fitBehavesLike(@"two passing shared examples (Objective-C)", ^NSDictionary *{ return @{}; }); 29 | 30 | QuickSpecEnd 31 | 32 | QuickSpecBegin(FunctionalTests_FocusedSpec_Unfocused) 33 | 34 | it(@"has an unfocused example thay fails, but is never run", ^{ XCTFail(); }); 35 | 36 | describe(@"an unfocused example group that is never run", ^{ 37 | beforeEach(^{ [NSException raise:NSInternalInconsistencyException format:@""]; }); 38 | it(@"has an example that fails, but is never run", ^{ XCTFail(); }); 39 | }); 40 | 41 | QuickSpecEnd 42 | 43 | @interface FocusedTests: XCTestCase 44 | @end 45 | 46 | @implementation FocusedTests 47 | 48 | - (void)testOnlyFocusedExamplesAreExecuted { 49 | XCTestRun *result = qck_runSpecs(@[ 50 | [FunctionalTests_FocusedSpec_Focused class], 51 | [FunctionalTests_FocusedSpec_Unfocused class] 52 | ]); 53 | XCTAssertEqual(result.executionCount, 5); 54 | } 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /QuickFocusedTests/FocusedTests.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | import XCTest 4 | 5 | class FunctionalTests_FocusedSpec_SharedExamplesConfiguration: QuickConfiguration { 6 | override class func configure(configuration: Configuration) { 7 | sharedExamples("two passing shared examples") { 8 | it("has an example that passes (4)") {} 9 | it("has another example that passes (5)") {} 10 | } 11 | } 12 | } 13 | 14 | class FunctionalTests_FocusedSpec_Focused: QuickSpec { 15 | override func spec() { 16 | it("has an unfocused example that fails, but is never run") { fail() } 17 | fit("has a focused example that passes (1)") {} 18 | 19 | fdescribe("a focused example group") { 20 | it("has an example that is not focused, but will be run, and passes (2)") {} 21 | fit("has a focused example that passes (3)") {} 22 | } 23 | 24 | // TODO: Port fitBehavesLike to Swift. 25 | itBehavesLike("two passing shared examples", flags: [Filter.focused: true]) 26 | } 27 | } 28 | 29 | class FunctionalTests_FocusedSpec_Unfocused: QuickSpec { 30 | override func spec() { 31 | it("has an unfocused example that fails, but is never run") { fail() } 32 | 33 | describe("an unfocused example group that is never run") { 34 | beforeEach { assert(false) } 35 | it("has an example that fails, but is never run") { fail() } 36 | } 37 | } 38 | } 39 | 40 | class FocusedTests: XCTestCase { 41 | func testOnlyFocusedExamplesAreExecuted() { 42 | let result = qck_runSpecs([ 43 | FunctionalTests_FocusedSpec_Focused.classForCoder(), 44 | FunctionalTests_FocusedSpec_Unfocused.classForCoder() 45 | ]) 46 | XCTAssertEqual(result.executionCount, 5 as UInt) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /QuickFocusedTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /QuickTests/Fixtures/FunctionalTests_SharedExamplesTests_SharedExamples.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | 4 | class FunctionalTests_SharedExamplesTests_SharedExamples: QuickConfiguration { 5 | override class func configure(configuration: Configuration) { 6 | sharedExamples("a group of three shared examples") { 7 | it("passes once") { expect(true).to(beTruthy()) } 8 | it("passes twice") { expect(true).to(beTruthy()) } 9 | it("passes three times") { expect(true).to(beTruthy()) } 10 | } 11 | 12 | sharedExamples("shared examples that take a context") { (sharedExampleContext: SharedExampleContext) in 13 | it("is passed the correct parameters via the context") { 14 | let callsite = sharedExampleContext()["callsite"] as! String 15 | expect(callsite).to(equal("SharedExamplesSpec")) 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /QuickTests/FunctionalTests/AfterEachTests+ObjC.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | 5 | #import "QCKSpecRunner.h" 6 | 7 | typedef NS_ENUM(NSUInteger, AfterEachType) { 8 | OuterOne, 9 | OuterTwo, 10 | OuterThree, 11 | InnerOne, 12 | InnerTwo, 13 | NoExamples, 14 | }; 15 | 16 | static NSMutableArray *afterEachOrder; 17 | 18 | QuickSpecBegin(FunctionalTests_AfterEachSpec) 19 | 20 | afterEach(^{ [afterEachOrder addObject:@(OuterOne)]; }); 21 | afterEach(^{ [afterEachOrder addObject:@(OuterTwo)]; }); 22 | afterEach(^{ [afterEachOrder addObject:@(OuterThree)]; }); 23 | 24 | it(@"executes the outer afterEach closures once, but not before this closure [1]", ^{ 25 | expect(afterEachOrder).to(equal(@[])); 26 | }); 27 | 28 | it(@"executes the outer afterEach closures a second time, but not before this closure [2]", ^{ 29 | expect(afterEachOrder).to(equal(@[@(OuterOne), @(OuterTwo), @(OuterThree)])); 30 | }); 31 | 32 | context(@"when there are nested afterEach", ^{ 33 | afterEach(^{ [afterEachOrder addObject:@(InnerOne)]; }); 34 | afterEach(^{ [afterEachOrder addObject:@(InnerTwo)]; }); 35 | 36 | it(@"executes the outer and inner afterEach closures, but not before this closure [3]", ^{ 37 | // The afterEach for the previous two examples should have been run. 38 | // The list should contain the afterEach for those example, executed from top to bottom. 39 | expect(afterEachOrder).to(equal(@[ 40 | @(OuterOne), @(OuterTwo), @(OuterThree), 41 | @(OuterOne), @(OuterTwo), @(OuterThree), 42 | ])); 43 | }); 44 | }); 45 | 46 | context(@"when there are nested afterEach without examples", ^{ 47 | afterEach(^{ [afterEachOrder addObject:@(NoExamples)]; }); 48 | }); 49 | 50 | QuickSpecEnd 51 | 52 | @interface AfterEachTests : XCTestCase; @end 53 | 54 | @implementation AfterEachTests 55 | 56 | - (void)setUp { 57 | [super setUp]; 58 | afterEachOrder = [NSMutableArray array]; 59 | } 60 | 61 | - (void)tearDown { 62 | afterEachOrder = [NSMutableArray array]; 63 | [super tearDown]; 64 | } 65 | 66 | - (void)testAfterEachIsExecutedInTheCorrectOrder { 67 | qck_runSpec([FunctionalTests_AfterEachSpec class]); 68 | NSArray *expectedOrder = @[ 69 | // [1] The outer afterEach closures are executed from top to bottom. 70 | @(OuterOne), @(OuterTwo), @(OuterThree), 71 | // [2] The outer afterEach closures are executed from top to bottom. 72 | @(OuterOne), @(OuterTwo), @(OuterThree), 73 | // [3] The outer afterEach closures are executed from top to bottom, 74 | // then the outer afterEach closures are executed from top to bottom. 75 | @(InnerOne), @(InnerTwo), @(OuterOne), @(OuterTwo), @(OuterThree), 76 | ]; 77 | 78 | XCTAssertEqualObjects(afterEachOrder, expectedOrder); 79 | } 80 | 81 | @end 82 | -------------------------------------------------------------------------------- /QuickTests/FunctionalTests/AfterEachTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Quick 3 | import Nimble 4 | 5 | private enum AfterEachType { 6 | case OuterOne 7 | case OuterTwo 8 | case OuterThree 9 | case InnerOne 10 | case InnerTwo 11 | case NoExamples 12 | } 13 | 14 | private var afterEachOrder = [AfterEachType]() 15 | 16 | class FunctionalTests_AfterEachSpec: QuickSpec { 17 | override func spec() { 18 | afterEach { afterEachOrder.append(AfterEachType.OuterOne) } 19 | afterEach { afterEachOrder.append(AfterEachType.OuterTwo) } 20 | afterEach { afterEachOrder.append(AfterEachType.OuterThree) } 21 | 22 | it("executes the outer afterEach closures once, but not before this closure [1]") { 23 | // No examples have been run, so no afterEach will have been run either. 24 | // The list should be empty. 25 | expect(afterEachOrder).to(beEmpty()) 26 | } 27 | 28 | it("executes the outer afterEach closures a second time, but not before this closure [2]") { 29 | // The afterEach for the previous example should have been run. 30 | // The list should contain the afterEach for that example, executed from top to bottom. 31 | expect(afterEachOrder).to(equal([AfterEachType.OuterOne, AfterEachType.OuterTwo, AfterEachType.OuterThree])) 32 | } 33 | 34 | context("when there are nested afterEach") { 35 | afterEach { afterEachOrder.append(AfterEachType.InnerOne) } 36 | afterEach { afterEachOrder.append(AfterEachType.InnerTwo) } 37 | 38 | it("executes the outer and inner afterEach closures, but not before this closure [3]") { 39 | // The afterEach for the previous two examples should have been run. 40 | // The list should contain the afterEach for those example, executed from top to bottom. 41 | expect(afterEachOrder).to(equal([ 42 | AfterEachType.OuterOne, AfterEachType.OuterTwo, AfterEachType.OuterThree, 43 | AfterEachType.OuterOne, AfterEachType.OuterTwo, AfterEachType.OuterThree, 44 | ])) 45 | } 46 | } 47 | 48 | context("when there are nested afterEach without examples") { 49 | afterEach { afterEachOrder.append(AfterEachType.NoExamples) } 50 | } 51 | } 52 | } 53 | 54 | class AfterEachTests: XCTestCase { 55 | override func setUp() { 56 | super.setUp() 57 | afterEachOrder = [] 58 | } 59 | 60 | override func tearDown() { 61 | afterEachOrder = [] 62 | super.tearDown() 63 | } 64 | 65 | func testAfterEachIsExecutedInTheCorrectOrder() { 66 | qck_runSpec(FunctionalTests_AfterEachSpec.classForCoder()) 67 | let expectedOrder = [ 68 | // [1] The outer afterEach closures are executed from top to bottom. 69 | AfterEachType.OuterOne, AfterEachType.OuterTwo, AfterEachType.OuterThree, 70 | // [2] The outer afterEach closures are executed from top to bottom. 71 | AfterEachType.OuterOne, AfterEachType.OuterTwo, AfterEachType.OuterThree, 72 | // [3] The inner afterEach closures are executed from top to bottom, 73 | // then the outer afterEach closures are executed from top to bottom. 74 | AfterEachType.InnerOne, AfterEachType.InnerTwo, 75 | AfterEachType.OuterOne, AfterEachType.OuterTwo, AfterEachType.OuterThree, 76 | ] 77 | XCTAssertEqual(afterEachOrder, expectedOrder) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /QuickTests/FunctionalTests/AfterSuiteTests+ObjC.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | 5 | #import "QCKSpecRunner.h" 6 | 7 | static BOOL afterSuiteWasExecuted = NO; 8 | 9 | QuickSpecBegin(FunctionalTests_AfterSuite_AfterSuiteSpec) 10 | 11 | afterSuite(^{ 12 | afterSuiteWasExecuted = YES; 13 | }); 14 | 15 | QuickSpecEnd 16 | 17 | QuickSpecBegin(FunctionalTests_AfterSuite_Spec) 18 | 19 | it(@"is executed before afterSuite", ^{ 20 | expect(@(afterSuiteWasExecuted)).to(beFalsy()); 21 | }); 22 | 23 | QuickSpecEnd 24 | 25 | @interface AfterSuiteTests : XCTestCase; @end 26 | 27 | @implementation AfterSuiteTests 28 | 29 | - (void)testAfterSuiteIsNotExecutedBeforeAnyExamples { 30 | // Execute the spec with an assertion after the one with an afterSuite. 31 | NSArray *specs = @[ 32 | [FunctionalTests_AfterSuite_AfterSuiteSpec class], 33 | [FunctionalTests_AfterSuite_Spec class] 34 | ]; 35 | XCTestRun *result = qck_runSpecs(specs); 36 | 37 | // Although this ensures that afterSuite is not called before any 38 | // examples, it doesn't test that it's ever called in the first place. 39 | XCTAssert(result.hasSucceeded); 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /QuickTests/FunctionalTests/AfterSuiteTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Quick 3 | import Nimble 4 | 5 | var afterSuiteWasExecuted = false 6 | 7 | class FunctionalTests_AfterSuite_AfterSuiteSpec: QuickSpec { 8 | override func spec() { 9 | afterSuite { 10 | afterSuiteWasExecuted = true 11 | } 12 | } 13 | } 14 | 15 | class FunctionalTests_AfterSuite_Spec: QuickSpec { 16 | override func spec() { 17 | it("is executed before afterSuite") { 18 | expect(afterSuiteWasExecuted).to(beFalsy()) 19 | } 20 | } 21 | } 22 | 23 | class AfterSuiteTests: XCTestCase { 24 | func testAfterSuiteIsNotExecutedBeforeAnyExamples() { 25 | // Execute the spec with an assertion after the one with an afterSuite. 26 | let specs = NSArray(objects: FunctionalTests_AfterSuite_AfterSuiteSpec.classForCoder(), 27 | FunctionalTests_AfterSuite_Spec.classForCoder()) 28 | let result = qck_runSpecs(specs as [AnyObject]) 29 | 30 | // Although this ensures that afterSuite is not called before any 31 | // examples, it doesn't test that it's ever called in the first place. 32 | XCTAssert(result.hasSucceeded) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /QuickTests/FunctionalTests/BeforeEachTests+ObjC.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | #import "QCKSpecRunner.h" 5 | 6 | typedef NS_ENUM(NSUInteger, BeforeEachType) { 7 | OuterOne, 8 | OuterTwo, 9 | InnerOne, 10 | InnerTwo, 11 | InnerThree, 12 | NoExamples, 13 | }; 14 | 15 | static NSMutableArray *beforeEachOrder; 16 | 17 | QuickSpecBegin(FunctionalTests_BeforeEachSpec) 18 | 19 | beforeEach(^{ [beforeEachOrder addObject:@(OuterOne)]; }); 20 | beforeEach(^{ [beforeEachOrder addObject:@(OuterTwo)]; }); 21 | 22 | it(@"executes the outer beforeEach closures once [1]", ^{}); 23 | it(@"executes the outer beforeEach closures a second time [2]", ^{}); 24 | 25 | context(@"when there are nested beforeEach", ^{ 26 | beforeEach(^{ [beforeEachOrder addObject:@(InnerOne)]; }); 27 | beforeEach(^{ [beforeEachOrder addObject:@(InnerTwo)]; }); 28 | beforeEach(^{ [beforeEachOrder addObject:@(InnerThree)]; }); 29 | 30 | it(@"executes the outer and inner beforeEach closures [3]", ^{}); 31 | }); 32 | 33 | context(@"when there are nested beforeEach without examples", ^{ 34 | beforeEach(^{ [beforeEachOrder addObject:@(NoExamples)]; }); 35 | }); 36 | 37 | QuickSpecEnd 38 | 39 | @interface BeforeEachTests : XCTestCase; @end 40 | 41 | @implementation BeforeEachTests 42 | 43 | - (void)setUp { 44 | beforeEachOrder = [NSMutableArray array]; 45 | [super setUp]; 46 | } 47 | 48 | - (void)tearDown { 49 | beforeEachOrder = [NSMutableArray array]; 50 | [super tearDown]; 51 | } 52 | 53 | - (void)testBeforeEachIsExecutedInTheCorrectOrder { 54 | qck_runSpec([FunctionalTests_BeforeEachSpec class]); 55 | NSArray *expectedOrder = @[ 56 | // [1] The outer beforeEach closures are executed from top to bottom. 57 | @(OuterOne), @(OuterTwo), 58 | // [2] The outer beforeEach closures are executed from top to bottom. 59 | @(OuterOne), @(OuterTwo), 60 | // [3] The outer beforeEach closures are executed from top to bottom, 61 | // then the inner beforeEach closures are executed from top to bottom. 62 | @(OuterOne), @(OuterTwo), @(InnerOne), @(InnerTwo), @(InnerThree), 63 | ]; 64 | 65 | XCTAssertEqualObjects(beforeEachOrder, expectedOrder); 66 | } 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /QuickTests/FunctionalTests/BeforeEachTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Quick 3 | import Nimble 4 | 5 | private enum BeforeEachType { 6 | case OuterOne 7 | case OuterTwo 8 | case InnerOne 9 | case InnerTwo 10 | case InnerThree 11 | case NoExamples 12 | } 13 | 14 | private var beforeEachOrder = [BeforeEachType]() 15 | 16 | class FunctionalTests_BeforeEachSpec: QuickSpec { 17 | override func spec() { 18 | beforeEach { beforeEachOrder.append(BeforeEachType.OuterOne) } 19 | beforeEach { beforeEachOrder.append(BeforeEachType.OuterTwo) } 20 | 21 | it("executes the outer beforeEach closures once [1]") {} 22 | it("executes the outer beforeEach closures a second time [2]") {} 23 | 24 | context("when there are nested beforeEach") { 25 | beforeEach { beforeEachOrder.append(BeforeEachType.InnerOne) } 26 | beforeEach { beforeEachOrder.append(BeforeEachType.InnerTwo) } 27 | beforeEach { beforeEachOrder.append(BeforeEachType.InnerThree) } 28 | 29 | it("executes the outer and inner beforeEach closures [3]") {} 30 | } 31 | 32 | context("when there are nested beforeEach without examples") { 33 | beforeEach { beforeEachOrder.append(BeforeEachType.NoExamples) } 34 | } 35 | } 36 | } 37 | 38 | class BeforeEachTests: XCTestCase { 39 | override func setUp() { 40 | super.setUp() 41 | beforeEachOrder = [] 42 | } 43 | 44 | override func tearDown() { 45 | beforeEachOrder = [] 46 | super.tearDown() 47 | } 48 | 49 | func testBeforeEachIsExecutedInTheCorrectOrder() { 50 | qck_runSpec(FunctionalTests_BeforeEachSpec.classForCoder()) 51 | let expectedOrder = [ 52 | // [1] The outer beforeEach closures are executed from top to bottom. 53 | BeforeEachType.OuterOne, BeforeEachType.OuterTwo, 54 | // [2] The outer beforeEach closures are executed from top to bottom. 55 | BeforeEachType.OuterOne, BeforeEachType.OuterTwo, 56 | // [3] The outer beforeEach closures are executed from top to bottom, 57 | // then the inner beforeEach closures are executed from top to bottom. 58 | BeforeEachType.OuterOne, BeforeEachType.OuterTwo, 59 | BeforeEachType.InnerOne, BeforeEachType.InnerTwo, BeforeEachType.InnerThree, 60 | ] 61 | XCTAssertEqual(beforeEachOrder, expectedOrder) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /QuickTests/FunctionalTests/BeforeSuiteTests+ObjC.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | 5 | #import "QCKSpecRunner.h" 6 | 7 | static BOOL beforeSuiteWasExecuted = NO; 8 | 9 | QuickSpecBegin(FunctionalTests_BeforeSuite_BeforeSuiteSpec) 10 | 11 | beforeSuite(^{ 12 | beforeSuiteWasExecuted = YES; 13 | }); 14 | 15 | QuickSpecEnd 16 | 17 | QuickSpecBegin(FunctionalTests_BeforeSuite_Spec) 18 | 19 | it(@"is executed after beforeSuite", ^{ 20 | expect(@(beforeSuiteWasExecuted)).to(beTruthy()); 21 | }); 22 | 23 | QuickSpecEnd 24 | 25 | @interface BeforeSuiteTests : XCTestCase; @end 26 | 27 | @implementation BeforeSuiteTests 28 | 29 | - (void)testBeforeSuiteIsExecutedBeforeAnyExamples { 30 | // Execute the spec with an assertion before the one with a beforeSuite 31 | NSArray *specs = @[ 32 | [FunctionalTests_BeforeSuite_Spec class], 33 | [FunctionalTests_BeforeSuite_BeforeSuiteSpec class] 34 | ]; 35 | XCTestRun *result = qck_runSpecs(specs); 36 | XCTAssert(result.hasSucceeded); 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /QuickTests/FunctionalTests/BeforeSuiteTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Quick 3 | import Nimble 4 | 5 | var beforeSuiteWasExecuted = false 6 | 7 | class FunctionalTests_BeforeSuite_BeforeSuiteSpec: QuickSpec { 8 | override func spec() { 9 | beforeSuite { 10 | beforeSuiteWasExecuted = true 11 | } 12 | } 13 | } 14 | 15 | class FunctionalTests_BeforeSuite_Spec: QuickSpec { 16 | override func spec() { 17 | it("is executed after beforeSuite") { 18 | expect(beforeSuiteWasExecuted).to(beTruthy()) 19 | } 20 | } 21 | } 22 | 23 | class BeforeSuiteTests: XCTestCase { 24 | func testBeforeSuiteIsExecutedBeforeAnyExamples() { 25 | // Execute the spec with an assertion before the one with a beforeSuite 26 | let specs = NSArray(objects: FunctionalTests_BeforeSuite_Spec.classForCoder(), 27 | FunctionalTests_BeforeSuite_BeforeSuiteSpec.classForCoder()) 28 | let result = qck_runSpecs(specs as [AnyObject]) 29 | 30 | XCTAssert(result.hasSucceeded) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /QuickTests/FunctionalTests/Configuration/AfterEach/Configuration+AfterEach.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | 3 | public var FunctionalTests_Configuration_AfterEachWasExecuted = false 4 | 5 | class FunctionalTests_Configuration_AfterEach: QuickConfiguration { 6 | override class func configure(configuration: Configuration) { 7 | configuration.afterEach { 8 | FunctionalTests_Configuration_AfterEachWasExecuted = true 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /QuickTests/FunctionalTests/Configuration/AfterEach/Configuration+AfterEachTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Quick 3 | import Nimble 4 | 5 | class Configuration_AfterEachSpec: QuickSpec { 6 | override func spec() { 7 | beforeEach { 8 | FunctionalTests_Configuration_AfterEachWasExecuted = false 9 | } 10 | it("is executed before the configuration afterEach") { 11 | expect(FunctionalTests_Configuration_AfterEachWasExecuted).to(beFalsy()) 12 | } 13 | } 14 | } 15 | 16 | class Configuration_AfterEachTests: XCTestCase { 17 | override func setUp() { 18 | super.setUp() 19 | FunctionalTests_Configuration_AfterEachWasExecuted = false 20 | } 21 | 22 | override func tearDown() { 23 | FunctionalTests_Configuration_AfterEachWasExecuted = false 24 | super.tearDown() 25 | } 26 | 27 | func testExampleIsRunAfterTheConfigurationBeforeEachIsExecuted() { 28 | qck_runSpec(Configuration_BeforeEachSpec.classForCoder()) 29 | XCTAssert(FunctionalTests_Configuration_AfterEachWasExecuted) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /QuickTests/FunctionalTests/Configuration/BeforeEach/Configuration+BeforeEach.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | 3 | public var FunctionalTests_Configuration_BeforeEachWasExecuted = false 4 | 5 | class FunctionalTests_Configuration_BeforeEach: QuickConfiguration { 6 | override class func configure(configuration: Configuration) { 7 | configuration.beforeEach { 8 | FunctionalTests_Configuration_BeforeEachWasExecuted = true 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /QuickTests/FunctionalTests/Configuration/BeforeEach/Configuration+BeforeEachTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Quick 3 | import Nimble 4 | 5 | class Configuration_BeforeEachSpec: QuickSpec { 6 | override func spec() { 7 | it("is executed after the configuration beforeEach") { 8 | expect(FunctionalTests_Configuration_BeforeEachWasExecuted).to(beTruthy()) 9 | } 10 | } 11 | } 12 | 13 | class Configuration_BeforeEachTests: XCTestCase { 14 | override func setUp() { 15 | super.setUp() 16 | FunctionalTests_Configuration_BeforeEachWasExecuted = false 17 | } 18 | 19 | override func tearDown() { 20 | FunctionalTests_Configuration_BeforeEachWasExecuted = false 21 | super.tearDown() 22 | } 23 | 24 | func testExampleIsRunAfterTheConfigurationBeforeEachIsExecuted() { 25 | qck_runSpec(Configuration_BeforeEachSpec.classForCoder()) 26 | XCTAssert(FunctionalTests_Configuration_BeforeEachWasExecuted) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /QuickTests/FunctionalTests/FailureTests+ObjC.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import 4 | #import 5 | 6 | #import "QCKSpecRunner.h" 7 | 8 | static BOOL isRunningFunctionalTests = NO; 9 | 10 | #pragma mark - Spec 11 | 12 | QuickSpecBegin(FunctionalTests_FailureSpec) 13 | 14 | describe(@"a group of failing examples", ^{ 15 | it(@"passes", ^{ 16 | expect(@YES).to(beTruthy()); 17 | }); 18 | 19 | it(@"fails (but only when running the functional tests)", ^{ 20 | expect(@(isRunningFunctionalTests)).to(beFalsy()); 21 | }); 22 | 23 | it(@"fails again (but only when running the functional tests)", ^{ 24 | expect(@(isRunningFunctionalTests)).to(beFalsy()); 25 | }); 26 | }); 27 | 28 | QuickSpecEnd 29 | 30 | #pragma mark - Tests 31 | 32 | @interface FailureTests : XCTestCase; @end 33 | 34 | @implementation FailureTests 35 | 36 | - (void)setUp { 37 | [super setUp]; 38 | isRunningFunctionalTests = YES; 39 | } 40 | 41 | - (void)tearDown { 42 | isRunningFunctionalTests = NO; 43 | [super tearDown]; 44 | } 45 | 46 | - (void)testFailureSpecHasSucceededIsFalse { 47 | XCTestRun *result = qck_runSpec([FunctionalTests_FailureSpec class]); 48 | XCTAssertFalse(result.hasSucceeded); 49 | } 50 | 51 | - (void)testFailureSpecExecutedAllExamples { 52 | XCTestRun *result = qck_runSpec([FunctionalTests_FailureSpec class]); 53 | XCTAssertEqual(result.executionCount, 3); 54 | } 55 | 56 | - (void)testFailureSpecFailureCountIsEqualToTheNumberOfFailingExamples { 57 | XCTestRun *result = qck_runSpec([FunctionalTests_FailureSpec class]); 58 | XCTAssertEqual(result.failureCount, 2); 59 | } 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /QuickTests/FunctionalTests/FailureUsingXCTAssertTests+ObjC.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | #import "QCKSpecRunner.h" 5 | 6 | static BOOL isRunningFunctionalTests = NO; 7 | 8 | QuickSpecBegin(FunctionalTests_FailureUsingXCTAssertSpec) 9 | 10 | it(@"fails using an XCTAssert (but only when running the functional tests)", ^{ 11 | XCTAssertFalse(isRunningFunctionalTests); 12 | }); 13 | 14 | it(@"fails again using an XCTAssert (but only when running the functional tests)", ^{ 15 | XCTAssertFalse(isRunningFunctionalTests); 16 | }); 17 | 18 | it(@"succeeds using an XCTAssert", ^{ 19 | XCTAssertTrue(YES); 20 | }); 21 | 22 | QuickSpecEnd 23 | 24 | #pragma mark - Tests 25 | 26 | @interface FailureUsingXCTAssertTests_ObjC : XCTestCase; @end 27 | 28 | @implementation FailureUsingXCTAssertTests_ObjC 29 | 30 | - (void)setUp { 31 | [super setUp]; 32 | isRunningFunctionalTests = YES; 33 | } 34 | 35 | - (void)tearDown { 36 | isRunningFunctionalTests = NO; 37 | [super tearDown]; 38 | } 39 | 40 | - (void)testFailureUsingXCTAssertSpecHasSucceededIsFalse { 41 | XCTestRun *result = qck_runSpec([FunctionalTests_FailureUsingXCTAssertSpec class]); 42 | XCTAssertFalse(result.hasSucceeded); 43 | } 44 | 45 | - (void)testFailureUsingXCTAssertSpecExecutedAllExamples { 46 | XCTestRun *result = qck_runSpec([FunctionalTests_FailureUsingXCTAssertSpec class]); 47 | XCTAssertEqual(result.executionCount, 3); 48 | } 49 | 50 | - (void)testFailureUsingXCTAssertSpecFailureCountIsEqualToTheNumberOfFailingExamples { 51 | XCTestRun *result = qck_runSpec([FunctionalTests_FailureUsingXCTAssertSpec class]); 52 | XCTAssertEqual(result.failureCount, 2); 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /QuickTests/FunctionalTests/ItTests+ObjC.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | 5 | #import "QCKSpecRunner.h" 6 | 7 | QuickSpecBegin(FunctionalTests_ItSpec) 8 | 9 | __block ExampleMetadata *exampleMetadata = nil; 10 | beforeEachWithMetadata(^(ExampleMetadata *metadata) { 11 | exampleMetadata = metadata; 12 | }); 13 | 14 | it(@" ", ^{ 15 | expect(exampleMetadata.example.name).to(equal(@" ")); 16 | }); 17 | 18 | it(@"has a description with セレクター名に使えない文字が入っている 👊💥", ^{ 19 | NSString *name = @"has a description with セレクター名に使えない文字が入っている 👊💥"; 20 | expect(exampleMetadata.example.name).to(equal(name)); 21 | }); 22 | 23 | QuickSpecEnd 24 | 25 | @interface ItTests : XCTestCase; @end 26 | 27 | @implementation ItTests 28 | 29 | - (void)testAllExamplesAreExecuted { 30 | XCTestRun *result = qck_runSpec([FunctionalTests_ItSpec class]); 31 | XCTAssertEqual(result.executionCount, 2); 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /QuickTests/FunctionalTests/ItTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Quick 3 | import Nimble 4 | 5 | class FunctionalTests_ItSpec: QuickSpec { 6 | override func spec() { 7 | var exampleMetadata: ExampleMetadata? 8 | beforeEach { metadata in exampleMetadata = metadata } 9 | 10 | it("") { 11 | expect(exampleMetadata!.example.name).to(equal("")) 12 | } 13 | 14 | it("has a description with セレクター名に使えない文字が入っている 👊💥") { 15 | let name = "has a description with セレクター名に使えない文字が入っている 👊💥" 16 | expect(exampleMetadata!.example.name).to(equal(name)) 17 | } 18 | } 19 | } 20 | 21 | class ItTests: XCTestCase { 22 | func testAllExamplesAreExecuted() { 23 | let result = qck_runSpec(FunctionalTests_ItSpec.classForCoder()) 24 | XCTAssertEqual(result.executionCount, 2 as UInt) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /QuickTests/FunctionalTests/PendingTests+ObjC.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | 5 | #import "QCKSpecRunner.h" 6 | 7 | static NSUInteger oneExampleBeforeEachExecutedCount = 0; 8 | static NSUInteger onlyPendingExamplesBeforeEachExecutedCount = 0; 9 | 10 | QuickSpecBegin(FunctionalTests_PendingSpec) 11 | 12 | pending(@"an example that will not run", ^{ 13 | expect(@YES).to(beFalsy()); 14 | }); 15 | 16 | describe(@"a describe block containing only one enabled example", ^{ 17 | beforeEach(^{ oneExampleBeforeEachExecutedCount += 1; }); 18 | it(@"an example that will run", ^{}); 19 | pending(@"an example that will not run", ^{}); 20 | }); 21 | 22 | describe(@"a describe block containing only pending examples", ^{ 23 | beforeEach(^{ onlyPendingExamplesBeforeEachExecutedCount += 1; }); 24 | pending(@"an example that will not run", ^{}); 25 | }); 26 | 27 | QuickSpecEnd 28 | 29 | @interface PendingTests : XCTestCase; @end 30 | 31 | @implementation PendingTests 32 | 33 | - (void)setUp { 34 | [super setUp]; 35 | oneExampleBeforeEachExecutedCount = 0; 36 | onlyPendingExamplesBeforeEachExecutedCount = 0; 37 | } 38 | 39 | - (void)tearDown { 40 | oneExampleBeforeEachExecutedCount = 0; 41 | onlyPendingExamplesBeforeEachExecutedCount = 0; 42 | [super tearDown]; 43 | } 44 | 45 | - (void)testAnOtherwiseFailingExampleWhenMarkedPendingDoesNotCauseTheSuiteToFail { 46 | XCTestRun *result = qck_runSpec([FunctionalTests_PendingSpec class]); 47 | XCTAssert(result.hasSucceeded); 48 | } 49 | 50 | - (void)testBeforeEachOnlyRunForEnabledExamples { 51 | qck_runSpec([FunctionalTests_PendingSpec class]); 52 | XCTAssertEqual(oneExampleBeforeEachExecutedCount, 1); 53 | } 54 | 55 | - (void)testBeforeEachDoesNotRunForContextsWithOnlyPendingExamples { 56 | qck_runSpec([FunctionalTests_PendingSpec class]); 57 | XCTAssertEqual(onlyPendingExamplesBeforeEachExecutedCount, 0); 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /QuickTests/FunctionalTests/PendingTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Quick 3 | import Nimble 4 | 5 | var oneExampleBeforeEachExecutedCount = 0 6 | var onlyPendingExamplesBeforeEachExecutedCount = 0 7 | 8 | class FunctionalTests_PendingSpec: QuickSpec { 9 | override func spec() { 10 | xit("an example that will not run") { 11 | expect(true).to(beFalsy()) 12 | } 13 | 14 | describe("a describe block containing only one enabled example") { 15 | beforeEach { oneExampleBeforeEachExecutedCount += 1 } 16 | it("an example that will run") {} 17 | pending("an example that will not run") {} 18 | } 19 | 20 | describe("a describe block containing only pending examples") { 21 | beforeEach { onlyPendingExamplesBeforeEachExecutedCount += 1 } 22 | pending("an example that will not run") {} 23 | } 24 | } 25 | } 26 | 27 | class PendingTests: XCTestCase { 28 | override func setUp() { 29 | super.setUp() 30 | oneExampleBeforeEachExecutedCount = 0 31 | onlyPendingExamplesBeforeEachExecutedCount = 0 32 | } 33 | 34 | override func tearDown() { 35 | oneExampleBeforeEachExecutedCount = 0 36 | onlyPendingExamplesBeforeEachExecutedCount = 0 37 | super.tearDown() 38 | } 39 | 40 | func testAnOtherwiseFailingExampleWhenMarkedPendingDoesNotCauseTheSuiteToFail() { 41 | let result = qck_runSpec(FunctionalTests_PendingSpec.classForCoder()) 42 | XCTAssert(result.hasSucceeded) 43 | } 44 | 45 | func testBeforeEachOnlyRunForEnabledExamples() { 46 | qck_runSpec(FunctionalTests_PendingSpec.classForCoder()) 47 | XCTAssertEqual(oneExampleBeforeEachExecutedCount, 1) 48 | } 49 | 50 | func testBeforeEachDoesNotRunForContextsWithOnlyPendingExamples() { 51 | qck_runSpec(FunctionalTests_PendingSpec.classForCoder()) 52 | XCTAssertEqual(onlyPendingExamplesBeforeEachExecutedCount, 0) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /QuickTests/FunctionalTests/SharedExamples+BeforeEachTests+ObjC.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | 5 | #import "QCKSpecRunner.h" 6 | 7 | static NSUInteger specBeforeEachExecutedCount = 0; 8 | static NSUInteger sharedExamplesBeforeEachExecutedCount = 0; 9 | 10 | QuickConfigurationBegin(FunctionalTests_SharedExamples_BeforeEachTests_SharedExamples) 11 | 12 | + (void)configure:(Configuration *)configuration { 13 | sharedExamples(@"a group of three shared examples with a beforeEach in Obj-C", 14 | ^(QCKDSLSharedExampleContext context) { 15 | beforeEach(^{ sharedExamplesBeforeEachExecutedCount += 1; }); 16 | it(@"passes once", ^{}); 17 | it(@"passes twice", ^{}); 18 | it(@"passes three times", ^{}); 19 | }); 20 | } 21 | 22 | QuickConfigurationEnd 23 | 24 | QuickSpecBegin(FunctionalTests_SharedExamples_BeforeEachSpec) 25 | 26 | beforeEach(^{ specBeforeEachExecutedCount += 1; }); 27 | it(@"executes the spec beforeEach once", ^{}); 28 | itBehavesLike(@"a group of three shared examples with a beforeEach in Obj-C", 29 | ^NSDictionary*{ return @{}; }); 30 | 31 | QuickSpecEnd 32 | 33 | @interface SharedExamples_BeforeEachTests : XCTestCase; @end 34 | 35 | @implementation SharedExamples_BeforeEachTests 36 | 37 | - (void)setUp { 38 | [super setUp]; 39 | specBeforeEachExecutedCount = 0; 40 | sharedExamplesBeforeEachExecutedCount = 0; 41 | } 42 | 43 | - (void)tearDown { 44 | specBeforeEachExecutedCount = 0; 45 | sharedExamplesBeforeEachExecutedCount = 0; 46 | [super tearDown]; 47 | } 48 | 49 | - (void)testBeforeEachOutsideOfSharedExamplesExecutedOnceBeforeEachExample { 50 | qck_runSpec([FunctionalTests_SharedExamples_BeforeEachSpec class]); 51 | XCTAssertEqual(specBeforeEachExecutedCount, 4); 52 | } 53 | 54 | - (void)testBeforeEachInSharedExamplesExecutedOnceBeforeEachSharedExample { 55 | qck_runSpec([FunctionalTests_SharedExamples_BeforeEachSpec class]); 56 | XCTAssertEqual(sharedExamplesBeforeEachExecutedCount, 3); 57 | } 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /QuickTests/FunctionalTests/SharedExamples+BeforeEachTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Quick 3 | import Nimble 4 | 5 | var specBeforeEachExecutedCount = 0 6 | var sharedExamplesBeforeEachExecutedCount = 0 7 | 8 | class FunctionalTests_SharedExamples_BeforeEachTests_SharedExamples: QuickConfiguration { 9 | override class func configure(configuration: Configuration) { 10 | sharedExamples("a group of three shared examples with a beforeEach") { 11 | beforeEach { sharedExamplesBeforeEachExecutedCount += 1 } 12 | it("passes once") {} 13 | it("passes twice") {} 14 | it("passes three times") {} 15 | } 16 | } 17 | } 18 | 19 | class FunctionalTests_SharedExamples_BeforeEachSpec: QuickSpec { 20 | override func spec() { 21 | beforeEach { specBeforeEachExecutedCount += 1 } 22 | it("executes the spec beforeEach once") {} 23 | itBehavesLike("a group of three shared examples with a beforeEach") 24 | } 25 | } 26 | 27 | class SharedExamples_BeforeEachTests: XCTestCase { 28 | override func setUp() { 29 | super.setUp() 30 | specBeforeEachExecutedCount = 0 31 | sharedExamplesBeforeEachExecutedCount = 0 32 | } 33 | 34 | override func tearDown() { 35 | specBeforeEachExecutedCount = 0 36 | sharedExamplesBeforeEachExecutedCount = 0 37 | super.tearDown() 38 | } 39 | 40 | func testBeforeEachOutsideOfSharedExamplesExecutedOnceBeforeEachExample() { 41 | qck_runSpec(FunctionalTests_SharedExamples_BeforeEachSpec.classForCoder()) 42 | XCTAssertEqual(specBeforeEachExecutedCount, 4) 43 | } 44 | 45 | func testBeforeEachInSharedExamplesExecutedOnceBeforeEachSharedExample() { 46 | qck_runSpec(FunctionalTests_SharedExamples_BeforeEachSpec.classForCoder()) 47 | XCTAssertEqual(sharedExamplesBeforeEachExecutedCount, 3) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /QuickTests/FunctionalTests/SharedExamplesTests+ObjC.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | 5 | #import "QCKSpecRunner.h" 6 | 7 | QuickSpecBegin(FunctionalTests_SharedExamples_Spec) 8 | 9 | itBehavesLike(@"a group of three shared examples", ^NSDictionary*{ return @{}; }); 10 | 11 | QuickSpecEnd 12 | 13 | QuickSpecBegin(FunctionalTests_SharedExamples_ContextSpec) 14 | 15 | itBehavesLike(@"shared examples that take a context", ^NSDictionary *{ 16 | return @{ @"callsite": @"SharedExamplesSpec" }; 17 | }); 18 | 19 | QuickSpecEnd 20 | 21 | QuickSpecBegin(FunctionalTests_SharedExamples_SameContextSpec) 22 | 23 | __block NSInteger counter = 0; 24 | 25 | afterEach(^{ 26 | counter++; 27 | }); 28 | 29 | sharedExamples(@"gets called with a different context from within the same spec file", ^(QCKDSLSharedExampleContext exampleContext) { 30 | 31 | it(@"tracks correctly", ^{ 32 | NSString *payload = exampleContext()[@"payload"]; 33 | BOOL expected = [payload isEqualToString:[NSString stringWithFormat:@"%ld", (long)counter]]; 34 | expect(@(expected)).to(beTrue()); 35 | }); 36 | 37 | }); 38 | 39 | itBehavesLike(@"gets called with a different context from within the same spec file", ^{ 40 | return @{ @"payload" : @"0" }; 41 | }); 42 | 43 | itBehavesLike(@"gets called with a different context from within the same spec file", ^{ 44 | return @{ @"payload" : @"1" }; 45 | }); 46 | 47 | QuickSpecEnd 48 | 49 | 50 | @interface SharedExamplesTests : XCTestCase; @end 51 | 52 | @implementation SharedExamplesTests 53 | 54 | - (void)testAGroupOfThreeSharedExamplesExecutesThreeExamples { 55 | XCTestRun *result = qck_runSpec([FunctionalTests_SharedExamples_Spec class]); 56 | XCTAssert(result.hasSucceeded); 57 | XCTAssertEqual(result.executionCount, 3); 58 | } 59 | 60 | - (void)testSharedExamplesWithContextPassContextToExamples { 61 | XCTestRun *result = qck_runSpec([FunctionalTests_SharedExamples_ContextSpec class]); 62 | XCTAssert(result.hasSucceeded); 63 | } 64 | 65 | @end 66 | -------------------------------------------------------------------------------- /QuickTests/FunctionalTests/SharedExamplesTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Quick 3 | import Nimble 4 | 5 | class FunctionalTests_SharedExamples_Spec: QuickSpec { 6 | override func spec() { 7 | itBehavesLike("a group of three shared examples") 8 | } 9 | } 10 | 11 | class FunctionalTests_SharedExamples_ContextSpec: QuickSpec { 12 | override func spec() { 13 | itBehavesLike("shared examples that take a context") { ["callsite": "SharedExamplesSpec"] } 14 | } 15 | } 16 | 17 | // Shared examples are defined in QuickTests/Fixtures 18 | class SharedExamplesTests: XCTestCase { 19 | func testAGroupOfThreeSharedExamplesExecutesThreeExamples() { 20 | let result = qck_runSpec(FunctionalTests_SharedExamples_Spec.classForCoder()) 21 | XCTAssert(result.hasSucceeded) 22 | XCTAssertEqual(result.executionCount, 3 as UInt) 23 | } 24 | 25 | func testSharedExamplesWithContextPassContextToExamples() { 26 | let result = qck_runSpec(FunctionalTests_SharedExamples_ContextSpec.classForCoder()) 27 | XCTAssert(result.hasSucceeded) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /QuickTests/Helpers/QCKSpecRunner.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | /** 4 | Runs an XCTestSuite instance containing only the given XCTestCase subclass. 5 | Use this to run QuickSpec subclasses from within a set of unit tests. 6 | 7 | Due to implicit dependencies in _XCTFailureHandler, this function raises an 8 | exception when used in Swift to run a failing test case. 9 | 10 | @param specClass The class of the spec to be run. 11 | @return An XCTestRun instance that contains information such as the number of failures, etc. 12 | */ 13 | extern XCTestRun *qck_runSpec(Class specClass); 14 | 15 | /** 16 | Runs an XCTestSuite instance containing the given XCTestCase subclasses, in the order provided. 17 | See the documentation for `qck_runSpec` for more details. 18 | 19 | @param specClasses An array of QuickSpec classes, in the order they should be run. 20 | @return An XCTestRun instance that contains information such as the number of failures, etc. 21 | */ 22 | extern XCTestRun *qck_runSpecs(NSArray *specClasses); 23 | -------------------------------------------------------------------------------- /QuickTests/Helpers/QCKSpecRunner.m: -------------------------------------------------------------------------------- 1 | #import "QCKSpecRunner.h" 2 | #import "XCTestObservationCenter+QCKSuspendObservation.h" 3 | #import "World.h" 4 | #import 5 | 6 | XCTestRun *qck_runSuite(XCTestSuite *suite) { 7 | [World sharedWorld].isRunningAdditionalSuites = YES; 8 | 9 | __block XCTestRun *result = nil; 10 | [[XCTestObservationCenter sharedTestObservationCenter] _suspendObservationForBlock:^{ 11 | if ([suite respondsToSelector:@selector(runTest)]) { 12 | [suite runTest]; 13 | result = suite.testRun; 14 | } else { 15 | #pragma clang diagnostic push 16 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 17 | result = [suite run]; 18 | #pragma clang diagnostic pop 19 | } 20 | }]; 21 | return result; 22 | } 23 | 24 | XCTestRun *qck_runSpec(Class specClass) { 25 | return qck_runSuite([XCTestSuite testSuiteForTestCaseClass:specClass]); 26 | } 27 | 28 | XCTestRun *qck_runSpecs(NSArray *specClasses) { 29 | XCTestSuite *suite = [XCTestSuite testSuiteWithName:@"MySpecs"]; 30 | for (Class specClass in specClasses) { 31 | [suite addTest:[XCTestSuite testSuiteForTestCaseClass:specClass]]; 32 | } 33 | 34 | return qck_runSuite(suite); 35 | } 36 | -------------------------------------------------------------------------------- /QuickTests/Helpers/QuickTestsBridgingHeader.h: -------------------------------------------------------------------------------- 1 | #import "QCKSpecRunner.h" 2 | -------------------------------------------------------------------------------- /QuickTests/Helpers/XCTestObservationCenter+QCKSuspendObservation.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | /** 4 | Expose internal XCTest class and methods in order to run isolated XCTestSuite 5 | instances while the QuickTests test suite is running. 6 | 7 | If an Xcode upgrade causes QuickTests to crash when executing, or for tests to fail 8 | with the message "Timed out waiting for IDE barrier message to complete", it is 9 | likely that this internal interface has been changed. 10 | */ 11 | @interface XCTestObservationCenter (QCKSuspendObservation) 12 | 13 | /** 14 | Suspends test suite observation for the duration that the block is executing. 15 | Any test suites that are executed within the block do not generate any log output. 16 | Failures are still reported. 17 | 18 | Use this method to run XCTestSuite objects while another XCTestSuite is running. 19 | Without this method, tests fail with the message: "Timed out waiting for IDE 20 | barrier message to complete". 21 | */ 22 | - (void)_suspendObservationForBlock:(void (^)(void))block; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /QuickTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /QuickTests/QuickConfigurationTests.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface QuickConfigurationTests : XCTestCase; @end 5 | 6 | @implementation QuickConfigurationTests 7 | 8 | - (void)testInitThrows { 9 | XCTAssertThrowsSpecificNamed([QuickConfiguration new], NSException, NSInternalInconsistencyException); 10 | } 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](http://f.cl.ly/items/0r1E192C1R0b2g2Q3h2w/QuickLogo_Color.png) 2 | 3 | Quick is a behavior-driven development framework for Swift and Objective-C. 4 | Inspired by [RSpec](https://github.com/rspec/rspec), [Specta](https://github.com/specta/specta), and [Ginkgo](https://github.com/onsi/ginkgo). 5 | 6 | ![](https://raw.githubusercontent.com/Quick/Assets/master/Screenshots/QuickSpec%20screenshot.png) 7 | 8 | ```swift 9 | // Swift 10 | 11 | import Quick 12 | import Nimble 13 | 14 | class TableOfContentsSpec: QuickSpec { 15 | override func spec() { 16 | describe("the 'Documentation' directory") { 17 | it("has everything you need to get started") { 18 | let sections = Directory("Documentation").sections 19 | expect(sections).to(contain("Organized Tests with Quick Examples and Example Groups")) 20 | expect(sections).to(contain("Installing Quick")) 21 | } 22 | 23 | context("if it doesn't have what you're looking for") { 24 | it("needs to be updated") { 25 | let you = You(awesome: true) 26 | expect{you.submittedAnIssue}.toEventually(beTruthy()) 27 | } 28 | } 29 | } 30 | } 31 | } 32 | ``` 33 | #### Nimble 34 | Quick comes together with [Nimble](https://github.com/Quick/Nimble) — a matcher framework for your tests. You can learn why `XCTAssert()` statements make your expectations unclear and how to fix that using Nimble assertions [here](./Documentation/NimbleAssertions.md). 35 | 36 | ## Documentation 37 | 38 | All documentation can be found in the [Documentation folder](./Documentation), including [detailed installation instructions](./Documentation/InstallingQuick.md) for CocoaPods, Carthage, Git submodules, and more. For example, you can install Quick and [Nimble](https://github.com/Quick/Nimble) using CocoaPods by adding the following to your Podfile: 39 | 40 | ```rb 41 | # Podfile 42 | 43 | use_frameworks! 44 | 45 | def testing_pods 46 | # If you're using Xcode 7 / Swift 2 47 | pod 'Quick', '~> 0.6.0' 48 | pod 'Nimble', '2.0.0-rc.3' 49 | 50 | # If you're using Xcode 6 / Swift 1.2 51 | pod 'Quick', '~> 0.3.0' 52 | pod 'Nimble', '~> 1.0.0' 53 | end 54 | 55 | target 'MyTests' do 56 | testing_pods 57 | end 58 | 59 | target 'MyUITests' do 60 | testing_pods 61 | end 62 | ``` 63 | 64 | ## License 65 | 66 | Apache 2.0 license. See the `LICENSE` file for details. 67 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | def run(command) 2 | system(command) or raise "RAKE TASK FAILED: #{command}" 3 | end 4 | 5 | namespace "test" do 6 | desc "Run unit tests for all iOS targets" 7 | task :ios do |t| 8 | run "xcodebuild -workspace Quick.xcworkspace -scheme Quick-iOS -destination 'platform=iOS Simulator,name=iPhone 6' clean test" 9 | end 10 | 11 | desc "Run unit tests for all OS X targets" 12 | task :osx do |t| 13 | run "xcodebuild -workspace Quick.xcworkspace -scheme Quick-OSX clean test" 14 | end 15 | end 16 | 17 | namespace "templates" do 18 | install_dir = File.expand_path("~/Library/Developer/Xcode/Templates/File Templates/Quick") 19 | src_dir = File.expand_path("../Quick Templates", __FILE__) 20 | 21 | desc "Install Quick templates" 22 | task :install do 23 | if File.exists? install_dir 24 | raise "RAKE TASK FAILED: Quick templates are already installed at #{install_dir}" 25 | else 26 | mkdir_p install_dir 27 | cp_r src_dir, install_dir 28 | end 29 | end 30 | 31 | desc "Uninstall Quick templates" 32 | task :uninstall do 33 | rm_rf install_dir 34 | end 35 | end 36 | 37 | task default: ["test:ios", "test:osx"] 38 | 39 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | xcode: 3 | version: "7.0" 4 | 5 | checkout: 6 | post: 7 | - git submodule update --init --recursive 8 | 9 | # despite what circle ci says, xctool 0.2.3 cannot run 10 | # ios simulator tests on iOS frameworks for whatever reason. 11 | # 12 | # See: https://github.com/facebook/xctool/issues/415 13 | test: 14 | override: 15 | - rake test:ios 16 | - rake test:osx 17 | 18 | 19 | --------------------------------------------------------------------------------