Ошибка с XCTestExpectation: нарушение API - несколько вызовов, сделанных для - [XCTestExpectation выполнить]

Я использую XCTestExpectations в Xcode 6 (Beta 5) для асинхронного тестирования. Все мои асинхронные тесты проходят индивидуально каждый раз, когда я их запускаю. Однако, когда я пытаюсь запустить весь пакет, некоторые тесты не проходят, и приложение выходит из строя.

Ошибка, которую я получаю, говорит API violation - multiple calls made to -[XCTestExpectation fulfill]. Действительно, это не верно в рамках одного метода; мой общий формат для моих тестов показан ниже:

- (void) someTest {
    /* Declare Expectation */
    XCTestExpectation *expectation = [self expectationWithDescription:@"My Expectation"];
    [MyClass loginOnServerWithEmail:@"[email protected]" andPassword:@"asdfasdf" onSuccess:^void(User *user) {
        /* Make some assertions here about the object that was given. */

        /* Fulfill the expectation */
        [expectation fulfill];
    }];

    [self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) {
        /* Error handling here */
    }];
}

Опять же, эти тесты проходят, когда запускаются индивидуально, и на самом деле они выполняют сетевые запросы (работают точно так, как предполагалось), но вместе, сбор тестов не запускается.

Я смог посмотреть этот пост здесь, но не смог заставить решение работать для меня.

Кроме того, я запускаю OSX Mavericks и использую Xcode 6 (Beta 5).

Ответы

Ответ 1

Я не думаю, что использование __weak или __block - хороший подход. Я написал много модульных тестов, используя XCTestExpectation для awhile и никогда не сталкивался с этой проблемой до сих пор. Я наконец выяснил, что это настоящая причина проблемы, которая потенциально может вызвать ошибки в моем приложении. Основная причина моей проблемы заключается в том, что startAsynchronousTaskWithDuration вызывает многократное выполнение completeHandler. После того, как я исправлю это, нарушение API исчезло!

[self startAsynchronousTaskWithDuration:4 completionHandler:^(id result, NSError *error) {
    XCTAssertNotNil(result);
    XCTAssertNil(error);
    [expectation fulfill];
}];

Хотя мне потребовалось несколько часов, чтобы исправить мои модульные тесты, но я пришел к пониманию ошибки нарушения API, которая поможет мне избежать будущей проблемы времени выполнения в моем приложении.

Ответ 2

Я думаю, вероятно, что у вас есть проблема с циклом сохранения, которая мешает выпустить ваш объект, который вызывает блок, в котором ожидание выполняется несколько раз.

В противном случае, если ожидается, что ваше ожидание будет вызвано несколько раз, я написал небольшое расширение, позволяющее указать количество ожиданий:

import XCTest

extension XCTestExpectation {
    private class IntWrapper {
        let value :Int!
        init?(value:Int?) {
            self.value = value
            if (value == nil) {
                return nil
            }
        }
    }

    private struct AssociatedKey {
        static var expectationCountKey = ""
    }

    var expectationCount:Int? {
        get {
            return objc_getAssociatedObject(self, &AssociatedKey.expectationCountKey) as? Int
        }
        set {
            objc_setAssociatedObject(self, &AssociatedKey.expectationCountKey, newValue, .OBJC_ASSOCIATION_RETAIN)
        }
    }

    func decrementalFulfill() {
        guard let expectationCount = self.expectationCount else {
            fulfill()
            return
        }
        self.expectationCount = expectationCount - 1
        if self.expectationCount <= 0 {
            fulfill()
        }
    }
}

полный код (с тестом:) здесь: https://gist.github.com/huguesbr/7d110bffd043e4d11f2886693c680b06

Ответ 4

Попробуйте объявить ваше ожиданиеWithDescription слабым и разворачивать необязательную "ожидаемую" переменную.

 weak var asyncExpectation = expectationWithDescription("expectation")

 check for options in the block.
 if let asyncExpectation = asyncExpectation{
    asyncExpectation.fulfill()
 }

Это позволяет избежать освобождения переменной asyncExpectation и вызывать ваше ожидание на ноль.