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

Я использую XCTestExpectations в Xcode 6 (бета-версия 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 (бета-версия 5).


person Mihir    schedule 06.08.2014    source источник
comment
Я запускал наборы из более чем 100 тестов, прежде чем использовать по существу идентичный формат, и никогда раньше не сталкивался с этой проблемой... Вы уверены, что нет отдельных тестовых случаев, в которых есть два выполнения?   -  person Mike    schedule 03.09.2014
comment
Возможно, обновитесь до бета-версии 6 и посмотрите, сохраняется ли проблема?   -  person Mike    schedule 03.09.2014
comment
@Mihir У меня возникла эта проблема, когда я вызвал выполнение внутри обработчика expectationForNotification. Я предполагаю, что ваш тест вызывает выполнение несколько раз. если вы добавите оператор журнала при вызове выполнения, вы увидите :)   -  person Daniel Galasko    schedule 18.09.2014
comment
Я и раньше получал ошибку API violation - multiple calls made to, но потом понял, что мне не хватает этой функции wait(for: [promise], timeout: 10). Но вы добавили waitForExpectations.   -  person Farras Doko    schedule 31.03.2021


Ответы (10)


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

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

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

person Green Berry    schedule 01.09.2016

У меня такая же ошибка при установке expectation.expectedFulfillmentCount.

expectation.assertForOverFulfill = false

Если expectation.assertForOverFulfill = true и fulfill() вызываются, когда они уже выполнены, возникает исключение.

person yoAlex5    schedule 17.06.2020

Завершение работы приложения из-за неперехваченного исключения «NSInternalInconsistencyException», причина: «Нарушение API — несколько вызовов для -[XCTestExpectation выполнить] для чего бы то ни было».

Я получаю ошибку выше со следующим кодом:

func testMultipleWaits() {
    let exp = expectation(description: "whatever")
    for _ in 0...10 {
        DispatchQueue.main.async {
            exp.fulfill()
        }
        wait(for: [exp], timeout: 1)
    }
}

По сути, данное ожидание может быть выполнено один раз, его также можно дождаться один раз.

Это означает, что следующее изменение все равно не исправит это, потому что вы все еще ждали его несколько раз. Это даст вам следующую ошибку.

не удалось: поймано «NSInternalInconsistencyException», «Нарушение API — ожидания можно ожидать только один раз, whatever уже ожидалось»

func testMultipleWaits() {
    let exp = expectation(description: "whatever")
    for i in 0...10 {
        DispatchQueue.main.async {
            if i == 6 {
                exp.fulfill()
            }
        }
        wait(for: [exp], timeout: 1)
    }
}

Что сбивает с толку, так это то, что, хотя вышеуказанное изменение все еще дает сбой, тест завершается и дает вам ошибку «Асинхронное ожидание не удалось: превышено время ожидания 1 секунды, с невыполненными ожиданиями: «независимо».

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


Исправление здесь устанавливает ожидание внутри цикла for. Таким образом, ожидание выполняется и ожидает один раз за ожидание.

func testMultipleWaits() {        
    for _ in 0...10 {
        let exp = expectation(description: "whatever")
        DispatchQueue.main.async {
            exp.fulfill()
        }
        wait(for: [exp], timeout: 1)
    }
}
person Honey    schedule 23.01.2020

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

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

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

person Hugues BR    schedule 07.04.2016

Люди, которые столкнулись с ошибкой нарушения API, возможно, self.expectation() вам не друг. Вместо этого вы можете попробовать обычную инициализацию:

let expectation = XCTestExpectation(description: "")

Подробности см. в этом ответе.

person Vladimir Vlasov    schedule 03.06.2020

Я не был уверен, почему он звонил несколько раз, я решил это, выполнив что-то вроде:

let tokenReceived = expectation(description: "Token Received")
var fulfilled = false
mymanager.fetchToken(task: "token", callbackBlock: {token, error in
            DispatchQueue.main.async {
                tokenString = token
                debugPrint("Token found:\(token)")
                if(!fulfilled)
                {
                    fulfilled.toggle()
                    tokenReceived.fulfill()
                }
            }
        })
person Damien Cooke    schedule 06.05.2021

Была такая же проблема на Xcode 12.5/Swift 5.

Проверьте, не переопределяете ли вы методы класса setUp и tearDown.

Должен быть:

override func setUp()
override func tearDown()

Вместо:

override class func setUp()
override class func tearDown()
person Marcos Reboucas    schedule 06.05.2021

Моя проблема также заключалась в проблеме с потоками с моим кодом. Я не удалял значение в словаре обработчиков завершения. Затем это значение было повторно использовано моим следующим тестом, снова вызвавшим обработчик завершения (а также снова выполнившим ожидание), что вызвало ошибку.

Обратите внимание: в ваших интересах найти/исправить их и не предполагать проблему с ожиданием или Xcode.

person devjme    schedule 09.06.2021

Вот, вероятно, ответ, который вы ищете:

XCTestExpectation: как избежать вызова выполнения метод после завершения контекста ожидания?

По крайней мере, это решило проблему для меня.

person Caro    schedule 12.01.2015
comment
Хотя это потенциально неплохой ответ, по сути, это ответ только по ссылке. Вы должны включить некоторую информацию из своих ссылок, чтобы, если информация, стоящая за ссылками, когда-либо была изменена/утеряна, ответ по-прежнему имел смысл. См. эту ссылку< /а> - person James Webster; 25.08.2015

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

 weak var asyncExpectation = expectationWithDescription("expectation")

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

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

person creative_rd    schedule 24.03.2016
comment
чувак ... ты даже не на том же языке ... ты говоришь о необязательной оболочке для вопроса Objective-C ... - person jlngdt; 30.06.2016