XCTestExpectation出错:API违规 - 多次调用 - [XCTestExpectation fulfill]

时间:2014-08-06 18:10:54

标签: unit-testing asynchronous ios8 xcode6 xctest

我在Xcode 6(Beta 5)中使用XCTestExpectations进行异步测试。我每次运行时都会单独传递所有异步测试。但是,当我尝试运行我的整个套件时,一些测试没有通过,应用程序崩溃了。

我得到的错误是API violation - multiple calls made to -[XCTestExpectation fulfill]。实际上,在单一方法中并非如此;我的测试的一般格式如下所示:

- (void) someTest {
    /* Declare Expectation */
    XCTestExpectation *expectation = [self expectationWithDescription:@"My Expectation"];
    [MyClass loginOnServerWithEmail:@"example@email.com" 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 */
    }];
}

同样,这些测试确实在单独运行时通过,并且它们实际上正在发出网络请求(完全按预期工作),但是一起,测试集合无法运行。

我能够看一下这篇文章here,但无法让解决方案为我工作。

此外,我正在运行OSX Mavericks并使用Xcode 6(Beta 5)。

10 个答案:

答案 0 :(得分:13)

我不认为使用__weak__block是一种好方法。我已经使用XCTestExpectation写了很多单元测试一段时间,直到现在才遇到这个问题。我终于发现问题的真正原因可能导致我的应用程序中的错误。我的问题的根本原因是startAsynchronousTaskWithDuration多次调用completionHandler。修复后,API违规消失了!

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

虽然我花了几个小时来修复我的单元测试,但我开始意识到API违规错误,这将有助于我避免在我的应用程序中出现运行时问题。

答案 1 :(得分:4)

设置WITH child AS (SELECT taskid, MIN(JobLogDateTime) AS Endtime FROM JobLog WHERE taskid = 456 GROUP BY taskid) SELECT MAX(j.JobLogDateTime) AS Startime, MIN(c.Endtime) AS Endtime FROM JobLog AS j JOIN child AS c ON j.TaskID = c.TaskID; 时出现相同的错误。

exception.expectedFulfillmentCount

如果exception.assertForOverFulfill = false exception.assertForOverFulfill = true在已实现时被调用,则会引发异常

答案 2 :(得分:1)

由于未捕获的异常而终止应用程序 “ 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)
    }
}

答案 3 :(得分:0)

我认为你可能会在某个地方出现一个保留周期问题,导致无法释放你的对象,而这个对象正在调用多次实现预期的块。

否则,如果这是预期的行为,你的期望被多次调用,我写了一个小扩展,允许指定期望数:

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 :(得分:0)

那些坚持API违规错误的人,可能self.expectation()不是您的朋友。相反,您可以尝试常规的初始化:

let expectation = XCTestExpectation(description: "")

检查this的详细信息。

答案 5 :(得分:0)

我不知道为什么它多次调用已完成,我通过执行以下操作解决了这个问题:

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()
                }
            }
        })

答案 6 :(得分:0)

在 Xcode 12.5 / Swift 5 上有同样的问题。

检查您是否没有覆盖 setUp 和 tearDown 'class' 方法。

应该是:

override func setUp()
override func tearDown()

代替:

override class func setUp()
override class func tearDown()

答案 7 :(得分:0)

我的问题也是我的代码的线程问题。我没有删除完成处理程序字典中的值。然后,该值被我的下一个测试再次调用完成处理程序(并再次满足预期),从而导致错误。

请注意:找到/修复这些符合您的最大利益,不要假设预期或 Xcode 有问题。

答案 8 :(得分:-1)

这可能是您正在寻找的答案:

XCTestExpectation: how to avoid calling the fulfill method after the wait context has ended?

它至少为我解决了这个问题。

答案 9 :(得分:-3)

尝试将expectationWithDescription声明为弱并打开可选的“预期”变量。

 weak var asyncExpectation = expectationWithDescription("expectation")

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

这可以避免重新分配asyncExpectation变量并在nil上调用您的期望。