はじめに
こんにちは。
突然ですがiOSのユニットテストで、XCTestを使って非同期のテスト書くのって結構めんどくさいですよね。
最近色々あってQuick/NimbleからXCTestに戻したんですが、NimbleにあったtoEventuallyが結構魅力で、XCTestでXCTExpectationやwait(for: [expectation], timeout: 1.0)とか毎回書くの面倒だなと思っていました。
そこで、XCTestでもNimbleのtoEventuallyのように簡単に非同期のAssertionが書けるExtensionを作ってみました。
XCTestExtensions
https://github.com/shindyu/XCTestExtensions
使い方
非同期処理の値を確認するときの例.
func test_XCTAssertTrueEventually() {
var value = false
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
value = true
}
XCTAssertTrueEventually(value) // success
}
UI関連のテストに適用した例.
(ViewControllerAのボタンをタップするとViewControllerBが表示される)
func test_tappedButton() {
var viewControllerA = ViewControllerA()
viewControllerA.button.sendActions(for: .touchUpInside)
XCTAssertTrueEventually(viewControllerA.presentedViewController is ViewControllerB) // success
}
実装
public func XCTAssertTrueEventually(_ expression: @escaping @autoclosure () throws -> Bool, timeout: TimeInterval = 1.0, pollInterval: TimeInterval = 0.1, file: StaticString = #file, line: UInt = #line) {
let expectation = XCTestExpectation()
for i in 0..<Int(timeout/pollInterval) {
DispatchQueue.main.asyncAfter(deadline: .now() + pollInterval * Double(i)) {
if (try! expression()) {
expectation.fulfill()
}
}
}
switchProcess(
by: XCTWaiter.wait(for: [expectation], timeout: timeout),
timedOutMessage: "XCTAssertNotNilEventually failed",
file: file,
line: line
)
}
private func switchProcess(by result: XCTWaiter.Result, timedOutMessage: String, file: StaticString = #file, line: UInt = #line) {
switch result {
case .completed:
break
case .timedOut:
XCTFail(timedOutMessage, file: file, line: line)
default:
XCTFail("resultに応じたmessageをいれると良い", file: file, line: line)
}
}
簡単な解説
実装自体はXCTestExpectationとXCTWaiterを使った非同期の処理にDispatchQueueのasyncAfterを組み合わせたものです。
timeoutとpollIntervalを元に一定間隔でexpressionを実行した結果からfulfill()が実行され、XCTWaiter.waitで得られる値から最終的な処理が決まるようになっています。
timeoutとpollIntervalは呼び出し時に設定できるのでテストごとに調節することも可能です。
インターフェースはXCTAssertを参考にしつつ、既存のメソッドを使うときに気づけるように末尾にEventuallyをつけるようにしました。
ただのwrapperといえばそうなんですが、使う時に毎回お作法のようにXCTestExpectationなどのコードを書かなくて良くなるので気に入ってます。
最後に
ちなみに今回書いたコードは、会社で「普段の案件業務と直接関係ないことしましょ〜」という日があったので思い立って書いたコードです。
ついでに何種類かAssertionをまとめてOSSにして公開してみました。
宜しければ使ってみていただいて、StarやPullRequestお待ちしてますmm