I am trying to write unit tests where I want my test case to wait for a variable in a certain class to change. So I create an expectation with a predicate and wait for the value to change using XCTWaiter().wait(for: [expectation], timeout: 2.0)
, which I assume is the correct method to use.
The following code works as expected:
class ExpectationTests: XCTestCase {
var x: Int = 0
private func start() {
_ = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in
self.x = 1
}
}
func test1() {
let predicate = NSPredicate(format: "x == 1")
let expectation = XCTNSPredicateExpectation(predicate: predicate, object: self)
start()
let result = XCTWaiter().wait(for: [expectation], timeout: 2.0)
switch result {
case .completed: XCTAssertEqual(x, 1)
case .timedOut: XCTFail()
default: XCTFail()
}
}
A variable (x) is set to 0 and then changes to 1 after 0.5s by the start() function. The predicate waits for that var (x) to change. That works: result
is set to .completed
and the var actually is set to 1. Yeah :-)
However, when the variable that I want to observe is not a local var, but is in a class somewhere, it no longer works. Consider the following code fragment:
class MyClass: NSObject {
var y: Int = 0
func start() {
_ = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in
self.y = 1
}
}
}
func test2() {
let myClass = MyClass()
let predicate = NSPredicate(format: "y == 1")
let expectation = XCTNSPredicateExpectation(predicate: predicate, object: myClass)
myClass.start()
let result = XCTWaiter().wait(for: [expectation], timeout: 2.0)
switch result {
case .completed: XCTAssertEqual(myClass.y, 1)
case .timedOut: XCTFail()
default: XCTFail()
}
}
It is quite similar to the first piece of code, but this always ends after 2 seconds with result
being .timedOut
. I can't see what I am doing wrong. I use a variable from object myClass
that I pass into the expectation instead of a local var and object 'self'. (The class var myClass.y
is actually set to 1 when the test ends.)
I tried replacing XCTNSPredicateExpectation(predicate:object)
with expectation(for:evaluatedWith:handler)
, but that didn't make any difference.
Many examples here on StackOverflow use a predicate that checks for exists
in XCUIElement
. But I am not testing UI; I just want to check if some var in some class has changed within a timeout period. I don't understand why that is so different from checking var exists
in XCUIElement
.
Any ideas?! Thank you in advance!
Well, thanks to @Willeke for pointing me in the right direction, I did find a solution, but I can't say I understand it completely... Here's what my code looks like now:
I can use a predicate with a closure that regularly checks whether the var has changed and returns true if it has the correct value. (It does that about once per second.) However, I actually thought that's what
XCTWaiter
was for, given the description in the documentation ofexpectation(for:evaluatedWith:handler:)
(which is a convenience method forXCTNSPredicateExpectation
):So, I am happy that I can move on, but I still don't understand why this doesn't work with
NSPredicate(format: "y == 1")
instead of the predicate with the closure...