ExpectToEventuallyEqual is an XCTest assertion for asynchronous code.
Let's say we have a table view that reads from a view model. So the view model determines the number of rows in the table:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
viewModel.result.count
}
The table view controller's viewDidLoad()
tells the view model to load, then reloads the table data. Because the load()
is asynchronous, we await its results and wrap this inside a Task
.
Task {
await viewModel.load()
self.tableView.reloadData()
}
If this Task
included a call to a closure, tests could wait on an XCTestExpectation
and inject a closure which calls fulfill()
on the expectation. So one approach to testing this is to add a completion closure that fires after the data reloads.
Another approach is to check for some condition in periodic intervals. After stubbing two results into the view model, here's an expectToEventuallyEqual
assertion that checks that the number of rows will eventually be 2:
try expectToEventuallyEqual(
actual: { tableDataSource.tableView(sut.tableView, numberOfRowsInSection: 0) },
expected: 2
)
The assertion repeatedly evaluates the actual
closure, comparing it to the expected
value. As soon as they are equal, this assertion will pass. If it times out with the values remaining unequal, the assertion fails.
There is a SampleApp you can try to see some passing tests and one failing test.
If you have a Project.swift
file, declare the following dependency:
dependencies: [
.package(url: "https://github.com/jonreid/ExpectToEventuallyEqual", from: "1.0.0"),
],
Then add it to your test target:
.testTarget(
name: "MyTests",
dependencies: [
"ExpectToEventuallyEqual",
],
The actual
closure is allowed to throw exceptions.
The actual
closure and and expected
value must evaluate to the same Equatable
type.
The default timeout
is 1 second. You can specify a different value.
After every comparison, the RunLoop
runs briefly to process other events on the thread.
On failure, the assertion reports the expected value and the last actual value. If they are strings, the values are shown in double quotes with the following characters represented as escaped special characters:
- \" (double quotation mark)
- \n (line feed)
- \r (carriage return)
- \t (horizontal tab)
Jon Reid and Steven Baker created this assertion while teaching an iOS workshop together.
- Jon is the author of iOS Unit Testing by Example. His website is Quality Coding.
- Steven is the inventor of RSpec and the describe/it pattern of testing. His website is stevenrbaker.com.