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のまま残ったりといったことがなくなるのだと思います。