{"action":"created","issue":{"url":"https://api.github.com/repos/Quick/Quick/issues/330","labels_url":"https://api.github.com/repos/Quick/Quick/issues/330/labels{/name}","comments_url":"https://api.github.com/repos/Quick/Quick/issues/330/comments","events_url":"https://api.github.com/repos/Quick/Quick/issues/330/events","html_url":"https://github.com/Quick/Quick/issues/330","id":89967239,"number":330,"title":"XCTestExpectation fulfill() in Quick test causes NSInternalInconsistencyException","user":{"login":"pcantrell","id":378162,"avatar_url":"https://avatars.githubusercontent.com/u/378162?v=3","gravatar_id":"","url":"https://api.github.com/users/pcantrell","html_url":"https://github.com/pcantrell","followers_url":"https://api.github.com/users/pcantrell/followers","following_url":"https://api.github.com/users/pcantrell/following{/other_user}","gists_url":"https://api.github.com/users/pcantrell/gists{/gist_id}","starred_url":"https://api.github.com/users/pcantrell/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pcantrell/subscriptions","organizations_url":"https://api.github.com/users/pcantrell/orgs","repos_url":"https://api.github.com/users/pcantrell/repos","events_url":"https://api.github.com/users/pcantrell/events{/privacy}","received_events_url":"https://api.github.com/users/pcantrell/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":1,"created_at":"2015-06-21T22:13:55Z","updated_at":"2015-06-22T15:48:16Z","closed_at":null,"body":"This code:\r\n\r\n    class QuickExpectationBug: QuickSpec {\r\n        override func spec() {\r\n            it(\"handles expectations correctly\") {\r\n                let expectation = self.expectationWithDescription(\"simple expectation\")\r\n                expectation.fulfill()\r\n                self.waitForExpectationsWithTimeout(0.1, handler: nil)\r\n            }\r\n        }\r\n    }\r\n\r\n…causes this exception:\r\n\r\n    Test Case '-[SiestaTests.QuickExpectationBug handles_expectations_correctly]' started.\r\n    2015-06-21 17:08:31.230 xctest[2044:34358] *** Assertion failure in -[XCTestExpectation fulfill], /Library/Caches/com.apple.xbs/Sources/XCTest_Sim/XCTest-8102.19/XCTestFramework/Classes/XCTestCase+AsynchronousTesting.m:399\r\n    /Users/paul/work/bustout/siesta/SiestaTests/QuickExpectationBug.swift:15: error: -[SiestaTests.QuickExpectationBug handles_expectations_correctly] : failed: caught \"NSInternalInconsistencyException\",\r\n      \"API violation - called -[XCTestExpectation fulfill] for simple expectation after\r\n      the test case in which the expectation was created is no longer running.\r\n      Created in test -[QuickExpectationBug (null)],\r\n      current test is -[QuickExpectationBug handles_expectations_correctly]\"\r\n\r\nThe same code works in an XCTestCase:\r\n\r\n    class ExpectationTest: XCTestCase {\r\n        func testExpectation() {\r\n            let expectation = self.expectationWithDescription(\"simple expectation\")\r\n            expectation.fulfill()\r\n            self.waitForExpectationsWithTimeout(0.1, handler: nil)\r\n        }\r\n    }\r\n\r\nSomething to do with Quick’s dynamically created test cases?\r\n\r\nUsing XC7.0 beta (7A120f), swift-2.0 branch of Quick."},"comment":{"url":"https://api.github.com/repos/Quick/Quick/issues/comments/114159618","html_url":"https://github.com/Quick/Quick/issues/330#issuecomment-114159618","issue_url":"https://api.github.com/repos/Quick/Quick/issues/330","id":114159618,"user":{"login":"pcantrell","id":378162,"avatar_url":"https://avatars.githubusercontent.com/u/378162?v=3","gravatar_id":"","url":"https://api.github.com/users/pcantrell","html_url":"https://github.com/pcantrell","followers_url":"https://api.github.com/users/pcantrell/followers","following_url":"https://api.github.com/users/pcantrell/following{/other_user}","gists_url":"https://api.github.com/users/pcantrell/gists{/gist_id}","starred_url":"https://api.github.com/users/pcantrell/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pcantrell/subscriptions","organizations_url":"https://api.github.com/users/pcantrell/orgs","repos_url":"https://api.github.com/users/pcantrell/repos","events_url":"https://api.github.com/users/pcantrell/events{/privacy}","received_events_url":"https://api.github.com/users/pcantrell/received_events","type":"User","site_admin":false},"created_at":"2015-06-22T15:48:16Z","updated_at":"2015-06-22T15:48:16Z","body":"This appears to be caused by the fact that +[QuickSpec initialize] builds all the Example instances in one go, using a shared dummy instance of the spec:\r\n\r\n    + (void)initialize {\r\n        ...\r\n        QuickSpec *spec = [self new];\r\n\r\n        @try {\r\n            [spec spec];   // ← All it() calls happen here\r\n        }\r\n        @catch (NSException *exception) {\r\n          ...\r\n        }\r\n    }\r\n\r\nAny it() closures that capture self are capturing that dummy instance.\r\n\r\nPossible solutions:\r\n\r\n**Option 1**: Re-run the spec method before each test, so that self captures the correct instance of QuickSpec, then discard all Example instances except the relevant one.\r\n\r\n  * Pros: Transparent. Makes the easy thing the correct thing.\r\n  * Cons: May cause unintended side effects if describe() / context() closures do inline setup work. Possible performance hit for large suites.\r\n\r\n**Option 2**: Allow it() closures to have the either the signature `QuickSpec -> ()` or the currently accepted `() -> ()`. If an example uses that new flavor, the closure is passed the correct QuickSpec instance, and code uses this instead of self:\r\n\r\n    it(\"handles expectations correctly\") { spec in\r\n        let expectation = spec.expectationWithDescription(\"simple expectation\")\r\n        expectation.fulfill()\r\n        spec.waitForExpectationsWithTimeout(0.1, handler: nil)\r\n    }\r\n\r\n**Option 3**: Provide a global method that returns the currently executing spec:\r\n\r\n    it(\"handles expectations correctly\") {\r\n        let expectation = QuickSpec.currentSpec().expectationWithDescription(\"simple expectation\")\r\n        expectation.fulfill()\r\n        QuickSpec.currentSpec().waitForExpectationsWithTimeout(0.1, handler: nil)\r\n    }\r\n\r\nI’m inclined to prefer option 2 if it doesn’t get too awkward with Swift’s finicky type checking, or option 3 if option 2 gets messy. @modocache, others, your thoughts?\r\n\r\nIs there a version of option 3 somewhere that I’m missing? Seems like a thing that might already exist."}}