I am trying to unit test a custom UIView
, which changes the UI asynchronously. This is the code for the custom view:
import UIKit
class DemoView: UIView {
var label: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
func setup() {
label = UILabel(frame: .zero)
self.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
self.centerXAnchor.constraint(equalTo: label.centerXAnchor).isActive = true
self.centerYAnchor.constraint(equalTo: label.centerYAnchor).isActive = true
}
@MainActor
func setLabel(_ text: String) {
Task {
try await Task.sleep(for: .milliseconds(100))
label.text = text
}
}
}
I want to test, that after calling the setLabel(_:)
function, the text on the label did change, therefore I wrote the following test:
@MainActor
func testExample() async throws {
let demoView = DemoView(frame: .zero)
XCTAssertEqual(demoView.label.text, nil)
demoView.setLabel("New Text")
let expectLabelChange = expectation(for: NSPredicate(block: { _, _ in
demoView.label.text != nil
}), evaluatedWith: demoView.label)
await waitForExpectations(timeout: 5)
XCTAssertEqual(demoView.label.text, "New Text")
}
But the exception runs into a timeout and the assert fails. When I set breakpoints, I can see that the Task
inside setLabel(_:)
is executed, but never reenters after sleeping, even though the timeout is long enough. Only after the waitForExpectations
finishes, the task inside setLabel(_:)
is continued, however this is too late for the assert to catch the changes.
How can I write the test, so that the Task
in setLabel(_:)
continues?
NOTE: The code is adjusted for demonstrating the issue. In the real app I call an API instead of sleeping.
You don't need
async
testing for this, and you shouldn't use it.setLabel
is notasync
, and you're using an expectation! This test will pass (and I've rewritten a few minor things along the way):Even better, remove the
@MainActor
from yoursetLabel
call; you can then remove it from the test function too.Under what circumstances would
async
for the test be appropriate? IfsetLabel
wereasync
! Suppose you rewritesetLabel
like this:Now you need your tests to be
async
— and now you don't need an expectation! Look how simple everything becomes: