XCTest: Wait for async call to finish in synchronous function [duplicate] - swift

Is there a way to wait for an async call to be finished when this call is wrapped in another method?
class Owner{
let dataManager = MockDataManager()
var data: String? = nil
func refresh() {
Task {
self.data = await dataManager.fetchData()
}
}
}
class MockDataManager {
var testData: String = "test"
func fetchData() async -> String {
testData
}
}
class OwnerTests: SKTestCase {
private var owner = Owner()
func testRefresh() {
owner.refresh()
XCTAssertEqual(owner.data, "test") // fail. the value is still nil
}
}
With callbacks, the tests used to work if everything under the hood was replaced with synchronous calls but here it looks like i am missing an operation to wait for a change to owner.data

Late to this party, but I agree with #Cristik regarding not changing the signature of a function just to accommodate testing. In the chat room conversation, #Cristik also pointed out a valid reason why a function can be set up to invoke an async function but yet not define its signature as async:
the Owner class may be in the nature of a view model (in an MVVM pattern) that exposes read-only observable/bindable (say #Published let) properties, that are bindable from (a) view(s), and the refresh function allows the view to request data update following a user event;
the refresh function isn't expected to return any data to the view when invoked, rather the view model (Owner) object will update the observable properties with the data returned while the views bound to (i.e. observing) the properties will be automatically updated.
In this case there's absolutely no need to mark the Owner.refresh() function as async and, thus forcing the view(s) to wrap their invocation of the refresh function in an async or Task (or .task modifier in SwiftUI) construct.
That said, I had similar situation and here's how I implemented the unit (not integration) test:
func testRefreshFunctionFetchesDataAndPopulatesFields() {
let expectation = XCTestExpectation(
description: "Owner fetches data and updates properties."
)
// `Owner` is the "subject under test", so use protocol-driven development
// and dependency injection to enable focusing on testing just the SUT
// unencumbered by peculiarities of the dependency
let owner = Owner(mockDataManager: DataManagerProtocol())
// Verify initial state
XCTAssertNil(owner.data)
owner.refresh()
let asyncWaitDuration = 0.5 // <= could be even less than 0.5 seconds even
DispatchQueue.main.asyncAfter(deadline: .now() + asyncWaitDuration) {
expectation.fulfill()
// Verify state after
XCTAssertEqual(owner.data, "someString")
}
wait(for: [expectation], timeout: asyncWaitDuration)
}
Hope this helps.

The fact that refresh detaches some async code to do its job, is an implementation detail, and your tests should not care about the implementation details.
Instead, focus on the behaviour of the unit. For example, in the scenario you posted, the expected behaviour is that sometime after refresh is called, owner.data should become "test". This is what you should assert against.
Your current test code follows the above good practice, only that, as you observed, it fails because it doesn't wait until the property ends up being set. So, try to fix this, but without caring how the async part is implemented. This will make your tests more robust, and your code easier to refactor.
One possible approach for validating the async update is to use a custom XCTestExpectation:
final class PropertyExpectation<T: AnyObject, V: Equatable>: XCTNSPredicateExpectation {
init(object: T, keyPath: KeyPath<T, V>, expectedValue: V) {
let predicate = NSPredicate(block: { _, _ in
return object[keyPath: keyPath] == expectedValue
})
super.init(predicate: predicate, object: nil)
}
}
func testRefresh() {
let exp = PropertyExpectation(object: owner, keyPath: \.data, expectedValue: "test")
owner.refresh()
wait(for: [exp], timeout: 5)
}
Alternatively, you can use a 3rd party library that comes with support for async assertions, like Nimble:
func testRefresh() {
owner.refresh()
expect(self.owner.data).toEventually(equal("test"))
}
As a side note, since your code is multithreaded, strongly recommending to add some synchronization in place, in order to avoid data races. The idiomatic way in regards to the structured concurrency is to convert your class into an actor, however, depending on how you're consuming the class from other parts of the code, it might not be a trivial task. Regardless, you should fix the data races conditions sooner rather than later.

I would like to contribute a solution for a more restricted situation where XCTestExpectation doesn't work, and that's when a view model is bound to the #MainActor, and you can't make every function call async (relying on property didSet). Waiting for expectations will also block the task in question, even a detached task won't help, the task will always execute after the test function. Storing and later awaiting the task solves the problem:
#MainActor
class ViewModel {
var task : Task<Void, Never>?
#Published var value1: Int = 0 {
didSet {
task = Task {
await update2()
}
}
}
#Published var value2: Int = 0
func update2() async {
value2 = value1 + 1
}
}
And then in the test:
func testExample() {
viewModel.value1 = 1
let _ = await viewModel.task?.result
XCTAssertEqual(viewModel.value2, 2)
}

func refresh() async {
self.data = await dataManager.fetchData()
}
then in the test await owner.refresh()
If you really need to wait synchronously for the async task, you can see this question Swift await/async - how to wait synchronously for an async task to complete?

Related

What is the Swift concurrency equivalent to a promise–resolver pair?

With the PromiseKit library, it’s possible to create a promise and a resolver function together and store them on an instance of a class:
class ExampleClass {
// Promise and resolver for the top news headline, as obtained from
// some web service.
private let (headlinePromise, headlineSeal) = Promise<String>.pending()
}
Like any promise, we can chain off of headlinePromise to do some work once the value is available:
headlinePromise.get { headline in
updateUI(headline: headline)
}
// Some other stuff here
Since the promise has not been resolved yet, the contents of the get closure will be enqueued somewhere and control will immediately move to the “some other stuff here” section; updateUI will not be called unless and until the promise is resolved.
To resolve the promise, an instance method can call headlineSeal:
makeNetworkRequest("https://news.example/headline").get { headline in
headlineSeal.fulfill(headline)
}
The promise is now resolved, and any promise chains that had been waiting for headlinePromise will continue. For the rest of the life of this ExampleClass instance, any promise chain starting like
headlinePromise.get { headline in
// ...
}
will immediately begin executing. (“Immediately” might mean “right now, synchronously,” or it might mean “on the next run of the event loop”; the distinction isn’t important for me here.) Since promises can only be resolved once, any future calls to headlineSeal.fulfill(_:) or headlineSeal.reject(_:) will be no-ops.
Question
How can this pattern be translated idiomatically into Swift concurrency (“async/await”)? It’s not important that there be an object called a “promise” and a function called a “resolver”; what I’m looking for is a setup that has the following properties:
It’s possible for some code to declare a dependency on some bit of asynchronously-available state, and yield until that state is available.
It’s possible for that state to be “fulfilled” from potentially any instance method.
Once the state is available, any future chains of code that depend on that state are able to run right away.
Once the state is available, its value is immutable; the state cannot become unavailable again, nor can its value be changed.
I think that some of these can be accomplished by storing an instance variable
private let headlineTask: Task<String, Error>
and then waiting for the value with
let headline = try await headlineTask.value
but I’m not sure how that Task should be initialized or how it should be “fulfilled.”
Here is a way to reproduce a Promise which can be awaited by multiple consumers and fulfilled by any synchronous code:
public final class Promise<Success: Sendable>: Sendable {
typealias Waiter = CheckedContinuation<Success, Never>
struct State {
var waiters = [Waiter]()
var result: Success? = nil
}
private let state = ManagedCriticalState(State())
public init(_ elementType: Success.Type = Success.self) { }
#discardableResult
public func fulfill(with value: Success) -> Bool {
return state.withCriticalRegion { state in
if state.result == nil {
state.result = value
for waiters in state.waiters {
waiters.resume(returning: value)
}
state.waiters.removeAll()
return false
}
return true
}
}
public var value: Success {
get async {
await withCheckedContinuation { continuation in
state.withCriticalRegion { state in
if let result = state.result {
continuation.resume(returning: result)
} else {
state.waiters.append(continuation)
}
}
}
}
}
}
extension Promise where Success == Void {
func fulfill() -> Bool {
return fulfill(with: ())
}
}
The ManagedCriticalState type can be found in this file from the SwiftAsyncAlgorithms package.
I think I got the implementation safe and correct but if someone finds an error I'll update the answer. For reference I got inspired by AsyncChannel and this blog post.
You can use it like this:
#main
enum App {
static func main() async throws {
let promise = Promise(String.self)
// Delayed fulfilling.
let fulfiller = Task.detached {
print("Starting to wait...")
try await Task.sleep(nanoseconds: 2_000_000_000)
print("Promise fulfilled")
promise.fulfill(with: "Done!")
}
let consumer = Task.detached {
await (print("Promise resolved to '\(promise.value)'"))
}
// Launch concurrent consumer and producer
// and wait for them to complete.
try await fulfiller.value
await consumer.value
// A promise can be fulfilled only once and
// subsequent calls to `.value` immediatly return
// with the previously resolved value.
promise.fulfill(with: "Ooops")
await (print("Promise still resolved to '\(promise.value)'"))
}
}
Short explanation
In Swift Concurrency, the high-level Task type resembles a Future/Promise (it can be awaited and suspends execution until resolved) but the actual resolution cannot be controlled from the outside: one must compose built-in lower-level asynchronous functions such as URLSession.data() or Task.sleep().
However, Swift Concurrency provides a (Checked|Unsafe)Continuation type which basically act as a Promise resolver. It is a low-lever building block which purpose is to migrate regular asynchronous code (callback-based for instance) to the Swift Concurrency world.
In the above code, continuations are created by the consumers (via the .value property) and stored in the Promise. Later, when the result is available the stored continuations are fulfilled (with .resume()), which resumes the execution of the consumers. The result is also cached so that if it is already available when .value is called it is directly returned to the called.
When a Promise is fulfilled multiple times, the current behavior is to ignore subsequent calls and to return aa boolean value indicating if the Promise was already fulfilled. Other API's could be used (a trap, throwing an error, etc.).
The internal mutable state of the Promise must be protected from concurrent accesses since multiple concurrency domains could try to read and write from it at the same time. This is achieve with regular locking (I believe this could have been achieved with an actor, though).

Swift: withCheckedContinuation and Dispatch QoSClass

I am adapting some old code which was using common completions in order to use the new async/await syntax with Parse SDK. Here is an example, this:
static func get(
className: String,
id: String,
_ completion: #escaping (PFObject?) -> Void
) {
let query = PFQuery(className: className)
query.getObjectInBackground(withId: id) { object, _ in
completion(object)
}
}
is becoming this:
static func get(
className: String,
objectId: String
) async -> PFObject? {
let query = PFQuery(className: className)
return await withCheckedContinuation { continuation in
query.getObjectInBackground(withId: objectId) { result, _ in
continuation.resume(returning: result)
}
}
}
However, I was also using DispatchQueue/QoS previously and so the old function actually looked like this:
static func get(
className: String,
id: String,
_ completion: #escaping (PFObject?) -> Void
) {
let query = PFQuery(className: className)
DispatchQueue.global(qos: .userInteractive).async {
query.getObjectInBackground(withId: id) { object, _ in
DispatchQueue.main.async {
completion(object)
}
}
}
}
How can I use this with the async/await syntax? Is it even needed?
Thank you for your help
You probably want to execute your async function get(className:objectId:) within a Swift Task, using this Task initialiser:
func foo(
className: String,
objectId: String
) async -> PFObject? {
await Task(priority: .userInitiated) {
await get(className: className, objectId: objectId)
}
.value
}
Note that, when using this Task initialiser you are getting "structured concurrency", which means, that the embedded task in function foo inherits the actor context of the calling function.
That is, you can use the result safely from whatever thread you called it.
That means also, if the task where function foo() is running, gets cancelled, the embedded task will be cancelled as well. Of course, cancellation is cooperatively, which means, you need to stop a running task explicitly. For example, as preferred in your use case, with a withTaskCancellationHandler, which calls cancel() on your PFQuery object. Or when you have a long running iterating task, you may poll the Task's cancellation status in reasonably steps while your task progresses.
Please also read about "detached" which behave differently regarding cancellation and inheriting the task priority.
As of your question whether it is needed:
I assume, you ask if using specifying a priority is needed:
Short answer: in many use cases it may be more safe to just inherit the priority from whatever thread the function originates.
In your case, I would not explicitly change it: when it is called from a background thread with low priority, your task should inheriting this priority as well. There will be no reason to make it high priority with userInitiated. Otherwise, if your function is already called from a user action, it will be inheriting this higher priority as well. I believe, this is what most use cases would require.
Also, when your worker is running in a background thread, as is the case in your code, it really doesn't matter much "how fast you schedule" this task.
So, basically you end up with your async get function as defined, use it as is, and all is good. ;)

Swift how to test async call wrapped in a function

Is there a way to wait for an async call to be finished when this call is wrapped in another method?
class Owner{
let dataManager = MockDataManager()
var data: String? = nil
func refresh() {
Task {
self.data = await dataManager.fetchData()
}
}
}
class MockDataManager {
var testData: String = "test"
func fetchData() async -> String {
testData
}
}
class OwnerTests: SKTestCase {
private var owner = Owner()
func testRefresh() {
owner.refresh()
XCTAssertEqual(owner.data, "test") // fail. the value is still nil
}
}
With callbacks, the tests used to work if everything under the hood was replaced with synchronous calls but here it looks like i am missing an operation to wait for a change to owner.data
Late to this party, but I agree with #Cristik regarding not changing the signature of a function just to accommodate testing. In the chat room conversation, #Cristik also pointed out a valid reason why a function can be set up to invoke an async function but yet not define its signature as async:
the Owner class may be in the nature of a view model (in an MVVM pattern) that exposes read-only observable/bindable (say #Published let) properties, that are bindable from (a) view(s), and the refresh function allows the view to request data update following a user event;
the refresh function isn't expected to return any data to the view when invoked, rather the view model (Owner) object will update the observable properties with the data returned while the views bound to (i.e. observing) the properties will be automatically updated.
In this case there's absolutely no need to mark the Owner.refresh() function as async and, thus forcing the view(s) to wrap their invocation of the refresh function in an async or Task (or .task modifier in SwiftUI) construct.
That said, I had similar situation and here's how I implemented the unit (not integration) test:
func testRefreshFunctionFetchesDataAndPopulatesFields() {
let expectation = XCTestExpectation(
description: "Owner fetches data and updates properties."
)
// `Owner` is the "subject under test", so use protocol-driven development
// and dependency injection to enable focusing on testing just the SUT
// unencumbered by peculiarities of the dependency
let owner = Owner(mockDataManager: DataManagerProtocol())
// Verify initial state
XCTAssertNil(owner.data)
owner.refresh()
let asyncWaitDuration = 0.5 // <= could be even less than 0.5 seconds even
DispatchQueue.main.asyncAfter(deadline: .now() + asyncWaitDuration) {
expectation.fulfill()
// Verify state after
XCTAssertEqual(owner.data, "someString")
}
wait(for: [expectation], timeout: asyncWaitDuration)
}
Hope this helps.
The fact that refresh detaches some async code to do its job, is an implementation detail, and your tests should not care about the implementation details.
Instead, focus on the behaviour of the unit. For example, in the scenario you posted, the expected behaviour is that sometime after refresh is called, owner.data should become "test". This is what you should assert against.
Your current test code follows the above good practice, only that, as you observed, it fails because it doesn't wait until the property ends up being set. So, try to fix this, but without caring how the async part is implemented. This will make your tests more robust, and your code easier to refactor.
One possible approach for validating the async update is to use a custom XCTestExpectation:
final class PropertyExpectation<T: AnyObject, V: Equatable>: XCTNSPredicateExpectation {
init(object: T, keyPath: KeyPath<T, V>, expectedValue: V) {
let predicate = NSPredicate(block: { _, _ in
return object[keyPath: keyPath] == expectedValue
})
super.init(predicate: predicate, object: nil)
}
}
func testRefresh() {
let exp = PropertyExpectation(object: owner, keyPath: \.data, expectedValue: "test")
owner.refresh()
wait(for: [exp], timeout: 5)
}
Alternatively, you can use a 3rd party library that comes with support for async assertions, like Nimble:
func testRefresh() {
owner.refresh()
expect(self.owner.data).toEventually(equal("test"))
}
As a side note, since your code is multithreaded, strongly recommending to add some synchronization in place, in order to avoid data races. The idiomatic way in regards to the structured concurrency is to convert your class into an actor, however, depending on how you're consuming the class from other parts of the code, it might not be a trivial task. Regardless, you should fix the data races conditions sooner rather than later.
I would like to contribute a solution for a more restricted situation where XCTestExpectation doesn't work, and that's when a view model is bound to the #MainActor, and you can't make every function call async (relying on property didSet). Waiting for expectations will also block the task in question, even a detached task won't help, the task will always execute after the test function. Storing and later awaiting the task solves the problem:
#MainActor
class ViewModel {
var task : Task<Void, Never>?
#Published var value1: Int = 0 {
didSet {
task = Task {
await update2()
}
}
}
#Published var value2: Int = 0
func update2() async {
value2 = value1 + 1
}
}
And then in the test:
func testExample() {
viewModel.value1 = 1
let _ = await viewModel.task?.result
XCTAssertEqual(viewModel.value2, 2)
}
func refresh() async {
self.data = await dataManager.fetchData()
}
then in the test await owner.refresh()
If you really need to wait synchronously for the async task, you can see this question Swift await/async - how to wait synchronously for an async task to complete?

Swift Concurrency - How to get a Result from a task not working

I tried to put this example into a simple project + ViewController but can't get it to compile
https://www.hackingwithswift.com/quick-start/concurrency/how-to-get-a-result-from-a-task
I'm trying to call fetchQuotes() from an IBAction via a button tap, however because fetchQuotes is marked async, I'm getting an error.
I know that I can wrap the call to fetchQuotes() in a task:
Task {
fetchQuotes()
}
, but this doesn't make sense to me since fetchQuotes is already creating tasks.
Can anyone advise?
Here is my code:
// https://www.hackingwithswift.com/quick-start/concurrency/how-to-get-a-result-from-a-task
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func buttonTap(_ sender: Any) {
fetchQuotes()
}
func fetchQuotes() async {
let downloadTask = Task { () -> String in
let url = URL(string: "https://hws.dev/quotes.txt")!
let data: Data
do {
(data, _) = try await URLSession.shared.data(from: url)
} catch {
throw LoadError.fetchFailed
}
if let string = String(data: data, encoding: .utf8) {
return string
} else {
throw LoadError.decodeFailed
}
}
let result = await downloadTask.result
do {
let string = try result.get()
print(string)
} catch LoadError.fetchFailed {
print("Unable to fetch the quotes.")
} catch LoadError.decodeFailed {
print("Unable to convert quotes to text.")
} catch {
print("Unknown error.")
}
}
}
enum LoadError: Error {
case fetchFailed, decodeFailed
}
Here's a screenshot of my code + Xcode error:
this doesn't make sense to me since fetchQuotes is already creating tasks
What fetchQuotes is internally doing is an implementation detail, it might as well not explicitly create any tasks, or not even be doing async calls.
Now, the async part of the method declaration tells the compiler the method might suspend the caller thread, thus any caller needs to provide an async context when calling the method. Note that I said might suspend not will suspend, this is a subtle, but important difference.
The tasks created by the method are valid only during the method execution, so if you need to be able to call the method from a non-async context you'll have to create new tasks.
However, you can tidy-up your code by providing a non-async overload:
func fetchQuotes() {
Task {
await fetchQuotes()
}
}
, which can be called from your #IBAction
#IBAction func buttonTap(_ sender: Any) {
fetchQuotes()
}
There's no escaping out of this, if you want to be able to call an async function from a non-async context, then you'll need a Task, regardless on how the called method is implemented. Structured concurrency tasks are not reusable.
but this doesn't make sense to me since fetchQuotes is already creating tasks
Irrelevant; you've internalized a false rule, or failed to internalize the real rule. This has nothing to do with who is "creating tasks". It has to do with the real rule, the most fundamental rule of Swift's async/await, which is:
You can only call an async method from within an async context.
Well, the method buttonTap is not an async method, so your call to fetchQuotes is not being made within an async context. That's illegal, and the compiler stops you dead.
But if you wrap the call to fetchQuotes in a Task initializer, that is an async context. Success!
Basically, the effect you're seeing is what I call the regress of doom. Since every call to an async method must be in an async context, but since your events from Cocoa, such as an IBAction, are not async, how can your code ever call an async method? The answer is a Task initializer: it gives you a place to get started.

Not getting all expected events when subscribing to sequence with TestScheduler

I'm trying to write an integration test for a Reactor in an app built with ReactorKit and Realm/RxRealm.
I'm having trouble using TestScheduler to simulate user actions and test the expected emitted states.
In a nutshell, my problem is this: I'm binding an action that will make my Reactor save an item to Realm, my Reactor also observes changes to this object in Realm, and I expect my Reactor to emit the new state of this item observed from Realm.
What I'm seeing is that my test does not get the emission of the newly saved object in time to assert its value, it's emitted after my test assertion runs.
There is a fair amount of code involved, but attempting to whittle it down into a self-contained example of what it all roughly looks like below:
struct MyObject {
var counter: Int = 0
}
class MyReactor: Reactor {
enum Action {
case load
case mutateState
}
enum Mutation {
case setObject(MyObject)
}
struct State {
var object: MyObject?
}
func mutate(action: Action) -> Observable<Mutation> {
switch action {
case .load:
return service.monitorObject().map(Mutation.setObject)
case .mutateState:
guard var myObject = currentState.object else { return .empty() }
myObject.counter += 1
return service.save(myObject).andThen(.empty())
}
}
func reduce(state: State, mutation: Mutation) -> Observable<State> {
var newState = state
switch mutation {
case let .setObject(object):
// Should be called twice in tests, once on load, once after mutateState action
newState.object = object
}
}
}
struct Service {
// There is always at least one default instance of `MyObject` in Realm.
func monitorObject() -> Observable<MyObject> {
return Observable
.collection(from: realm.objects(MyObject.self))
.map { $0.first! }
}
func save(_ object: MyObject) -> Completable {
return Completable.create { emitter in
try! realm.write {
realm.add(object, update: .modified)
}
emitter(.completed)
return Disposables.create()
}
}
}
class MyTest: QuickSpec {
var scheduler: TestScheduler!
var sut: MyReactor!
var disposeBag: DisposeBag!
var service: Service!
var config: Realm.Configuration!
override func spec() {
beforeEach {
config = Realm.Configuration(inMemoryIdentifier: UUID().uuidString)
scheduler = TestScheduler(initialClock: 0)
disposeBag = DisposeBag()
sut = MyReactor()
service = Service(realmConfig: config)
}
describe("when my reactor gets a mutateState action") {
it("should mutate state") {
scheduler.createHotObservable([
.next(1, Action.load),
.next(2, Action.mutateState),
])
.bind(to: sut.action)
.disposed(by: disposeBag)
let response = scheduler.start(created: 0, subscribed: 0, disposed: 1000) {
sut.state.map(\.object)
}
// Counter always equals 0
XCTAssertTrue(response.events.last!.value.element!!.counter == 1)
}
}
}
}
What I'm expecting to happen is my Reactor's state is set for a 2nd time, before the XCTAssertTrue is hit. What is actually happening is the assert is hit with the initially loaded state, and then, my reactor's state is set again.
I thought my problem might be related to schedulers. Something I tried was injecting the test scheduler into my Service and doing observeOn(testScheduler) on my monitorObject function. But I'm still observing the assert get hit before the reactor's state is set for the 2nd time. I'm also not sure if a nuance of RxRealm/Realm change set notifications is the cause - not sure how to verify whether that might be the case.
Hopefully the problem and question is clear. Thanks in advance for any help.
I decided attempting to write an integration test was more trouble than it was worth and probably not going to result in very useful tests anyway.
So you are trying to test to see if Realm works. I don't use Realm, but based on your description, it probably updates the object on an internal thread and then you get the emission on a subsequent cycle.
You can test it by using an XCTestExpectation. Here is documentation from Apple: https://developer.apple.com/documentation/xctest/asynchronous_tests_and_expectations/testing_asynchronous_operations_with_expectations
Note however, that if something goes wrong in Realm and this test fails, there isn't anything you can do about it.