Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XCTest: Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Parameter "test" must not be nil.'

Getting this error inconsistently when running some unit tests:

2018-12-20 09:11:34.892 MyApp[4530:106103] * Assertion failure in void _XCTFailureHandler(XCTestCase *__strong _Nonnull, BOOL, const char * _Nonnull, NSUInteger, NSString *__strong _Nonnull, NSString *__strong _Nullable, ...)(), /Library/Caches/com.apple.xbs/Sources/XCTest_Sim/XCTest-14460.20/Sources/XCTestFramework/Core/XCTestAssertionsImpl.m:41 2018-12-20 09:11:34.929 MyApp[4530:106103] * Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Parameter "test" must not be nil.'

Seems like some assertion is failing because a parameter is nil, but I'm having a hard time figuring out which one.

Environment: Xcode 10.1 iOS application

like image 411
Eneko Alonso Avatar asked Dec 20 '18 17:12

Eneko Alonso


1 Answers

XCTest assertions that are evaluated after the test "finishes" will throw this exception if the assertion fails:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Parameter "test" must not be nil.'

A basic example to prove this is the following test:

func testRaceCondition() {
    DispatchQueue.main.async {
        XCTAssertEqual(1 + 1, 3) // Assertion fails and 'nil' exception is thrown 💥
    }
}

The assertion is run asynchronously, but the test does not wait for the asynchronous block to finish. Thus, by the time the assertion is evaluated, the test has already finished and the test case has been released (and thus is nil).

Horror

The above code will not throw any errors if the assertions were to pass. The following code would appear to pass the test, but it is dangerous in the sense that failures will throw the above exception, instead of properly failing the test:

func testRaceCondition() {
    DispatchQueue.main.async {
        XCTAssertEqual(1 + 1, 2) // Assertion passes 😱
    }
}

Solution

To prevent this issue, all tests that evaluate assertions in a block executed asynchronously should use expectations and wait for them to complete:

func testRaceCondition() {
    let asyncExpectation = expectation(description: "Async block executed")
    DispatchQueue.main.async {
        XCTAssertEqual(1 + 1, 3)
        asyncExpectation.fulfill()
    }
    waitForExpectations(timeout: 1, handler: nil)
}

By using expectations, we will get a proper failing test error, instead of the hard-to-debug exception posted above:

XCTAssertEqual failed: ("2") is not equal to ("3")

like image 119
Eneko Alonso Avatar answered Oct 23 '22 17:10

Eneko Alonso