AppleのSwift Blogを読んで@auto_closureが面白そうだと思い活用方法を考えてみました。 @auto_closureはclosureを取る引数につけると、呼び出し側はclosureの{}を省略できるようになるというものです。 Swift Blogによると、AppleはSwiftでのassert()などの実装にこれを利用しているそうです。 今回はこれを利用して、条件が成立するまで待つXCTestExpectationを実装してみました。

以下のように使うことができます。

func testBlockOperation() {
    let queue = NSOperationQueue()
    let operation = NSBlockOperation(block: {
        NSThread.sleepForTimeInterval(1.0)
    })
    
    queue.addOperation(operation)
    
    expectationWithCondition(operation.finished)
    waitForExpectationsWithTimeout(10.0, handler: nil)
}

expectationWithCondition:が今回実装したメソッドです。 この例ではoperation.finished == trueとなるまで待つというexpectationがつくられています。 引数は@auto_closureになっているので、operation.finishedはclosure { operation.finished }として渡されます。 expectationWithCondition:の実装は以下のようになっており、渡されたclosureを反復して実行します。 closureがtrueを返した場合にはexpectationをfulfillし、falseを返した場合には反復実行を継続します。

import Foundation
import XCTest

extension XCTestCase {
    func iterateTestingCondition(condition: @auto_closure () -> LogicValue, expectation: XCTestExpectation?) {
        weak var weakExpectation = expectation
        
        if condition().getLogicValue() {
            weakExpectation?.fulfill()
        } else if weakExpectation {
            let delay = 0.05 * Double(NSEC_PER_SEC)
            let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
            let queue = dispatch_get_main_queue()
            dispatch_after(time, queue) {
                self.iterateTestingCondition(condition, expectation: weakExpectation)
            }
        }
    }
    
    func expectationWithCondition(condition: @auto_closure () -> LogicValue) -> XCTestExpectation {
        let expectation = expectationWithDescription("expectationWithCondition:")
        iterateTestingCondition(condition, expectation: expectation);
        return expectation
    }
}

テストケースの終了後にも反復実行が続いてしまうと困るので、expectationが生存しているかどうかで反復実行を続けるか判断します。 iterateTestingCondition:内ではexpectationを弱参照しているので、テストケースが終了すると iterateTestingCondition:内のexpectatioがnilとなり、メソッドの呼び出しが終了します。

反復実行は力技っぽくて嫌だなーと思いつつも、非同期処理の完了をcompletion handler, KVO, NSNotificationなどで受け取れることが難しい場合もあるので、そういうときに使えるかもしれません。

ishkawa/XCTestCase-ExpectationWithCondition