3

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")
        }
   }
}
ozool
  • 94
  • 1
  • 9

2 Answers2

0

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
        }
    }
}
Jochen Holzer
  • 1,598
  • 19
  • 25
0

I did a quick research in my tests and notice two things: 1. Like J.D. Wooder correctly says, you should inherit your production class from the NSObject (this is what gives you an opportunity to call obj-c runtime methods and use KeyValue methods). 2. The test doesn't crash anymore but still fails. To fix this mark your areWeDone variable with @objc specifier (so it looks like @objc var areWeDone). In my case, it works.

Vladlex
  • 845
  • 8
  • 16