LoginSignup
4
0

More than 5 years have passed since last update.

XCTAssertの非同期Assertion(XCTAssertEventually)作ってみた

Posted at

はじめに

こんにちは。
突然ですが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をつけるようにしました。
image.png

ただのwrapperといえばそうなんですが、使う時に毎回お作法のようにXCTestExpectationなどのコードを書かなくて良くなるので気に入ってます。

最後に

ちなみに今回書いたコードは、会社で「普段の案件業務と直接関係ないことしましょ〜」という日があったので思い立って書いたコードです。
ついでに何種類かAssertionをまとめてOSSにして公開してみました。
宜しければ使ってみていただいて、StarやPullRequestお待ちしてますmm

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0