NSBlogger

意識高いブログ

XCTestExpectationの「API violation」エラーの解決策

XCTestExpectation

XCTestで非同期通信処理をテストしたいときに使えるクラスです。

    XCTestExpectation *expectation = [self expectationWithDescription:@"successfully load image"];
    
    [self.imageLoader getRequest:@"http://dummy.jpg/"
                         success:^(UIImage* image){
                             XCTAssertNotNil(image);
                             [expectation fulfill];
                         }
                         failure:^(NSError *error){
                             XCTFail(@"image should be loaded.");
                         }];

    [self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) {
        XCTFail(@"time over.");
    }];

expectationを定義したあと、waitForExpectationsWithTimeout:handler:で指定した秒数の間、通信の処理を待つことができます。
上記の例だと5.0秒経ってしまうと「time over」となりエラー。
無事非同期通信が終わり、success:に結果が返ってきたら「fulfill」することで、非同期通信処理を完了させています。

API violationエラー

すべてのUnit Testを流しているときに、非同期通信まわりでよく下記のようなエラーに遭遇しました。

CRASH: API violation - multiple calls made to -[XCTestExpectation fulfill].

どうやら複数回「fulfill」を呼ぼうとしているようなエラーメッセージです。

CRASH: API violation - called -[XCTestExpectation fulfill] after the wait context has ended.

こういうのもありました。waitが終わってるのにfulfillを呼ぶといったエラーです。

どういう場合に起こるのか

1つ目は、たとえば画像を一度に5枚取得しようとして、返り先が同じメソッドだったりすると、「fulfill」が何度も呼ばれることがあります。こういう場合に起こり得ます。
2つ目は、waitで指定した時間以上経ったあとに「fulfill」を呼ぶと発生します。どうやら、waitで指定した時間が経つと、expectationがnilになるようです。

これらのエラーに遭遇したときに厄介だったのが、起こったり起こらなかったり、起こるテストがころころ変わったりするところです。一体どこが悪いのか探すのに苦労しました。

解決策

@property (nonatomic, weak) XCTestExpectation *imageLoaderExpectation;
__weak XCTestExpectation *expectation = [self expectationWithDescription:@"successfully load image"];

このようにexpectationを宣言するときに、「__weak」を指定してあげると直りました。fulfillが一度呼ばれたタイミングやwaitの時間が経ったときに、expectationがうまく解放され、2回呼ばれたりnilのまま残ったりといったことがなくなるのだと思います。