Search code examples
iosswiftunit-testingasynchronousxctestcase

XCTest: Can expectationForPredicate fulfill on async variable change?


I'm using expectation(for:evaluatedWith:handler:) to watch a variable in production code for a change, but it's never fulfilled - why?

I'd rather not clutter my production code by adding artificial completion blocks or notifications.

class ProductionClass {
    var areWeDone = false

    func doSomeStuff() {
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) {
            self.areWeDone = true
        }
    }
}

class Test: XCTestCase {
    override func setUp() { }
    override func tearDown() { }

    func testDoSomeStuff() {
        let productionClass = ProductionClass()
        let predicate = NSPredicate(format: "areWeDone = %d", true)
        let exp = expectation(for: predicate, evaluatedWith: productionClass, handler: nil)

        productionClass.doSomeStuff()

        let result = XCTWaiter.wait(for: [exp], timeout: 3)
        if result != XCTWaiter.Result.completed {
            XCTAssert(false, "areWeDone changed but test timeout")
        }
   }
}

Solution

  • The solution is quite easy - just make sure that the class "ProductionClass" inherits from NSObject and your test will work as expected:

    import Foundation
    
    class ProductionClass : NSObject {
        var areWeDone = false
    
        func doSomeStuff() {
            DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) {
                self.areWeDone = true
            }
        }
    }