How to assert an error is thrown async when testing? - swift

We can test thrown errors with XCTAssertThrowsError. Async things can be tested with expectation. I have some method which dispatch work to a background thread and can at some point throw an error.
Is it possible to expect an error be thrown somewhere in the future? I need to combine expectation and XCTAssertThrowsError I think, but I do not know how.
Reproduction project: https://github.com/Jasperav/ThrowingAsyncError. Just clone the project and run the tests, one of them will fail. I made a class which will crash after a few seconds after it has been allocated. I want to make sure it keeps crashing after a few seconds, so I want a test case for it.

You could fulfill an expectation in the catch block of a call that is expected to fail.
func testFailingAsyncCode() async throws {
let expectation = expectation(description: "expect call to throw error")
let dataFetcher = DataFetcher()
do {
// This call is expected to fail
let data = try await dataFetcher.fetchData(withRequest: request, validStatusCodes: [200])
} catch {
// Expectation is fulfilled when call fails
expectation.fulfill()
}
wait(for: [expectation], timeout: 3)
}

I took the sample code from David B.'s answer and changed it, because expectations are not needed when the unit test method is annotated with async.
func testFailingAsyncCode() async { // async is important here
let dataFetcher = DataFetcher()
var didFailWithError: Error?
do {
// This call is expected to fail
_ = try await dataFetcher.fetchData(withRequest: request, validStatusCodes: [200])
} catch {
didFailWithError = error
// Here you could do more assertions with the non-nil error object
}
XCTAssertNotNil(didFailWithError)
}

I took a look at the reproduction project to see what you were trying to accomplish here...
To my understanding:
XCTAssertThrowsError are assertions that takes in a block that can throw. They just happen to assert that an error is thrown in a synchronous block when it's done running.
XCTestExpectation are classes that keep track of whether or not requested conditions are met. They are for keeping track of asynchronous code behavior objects/references need to be kept and checked later.
What you seem to be trying to do is make something like XCTestExpectation work the same way XCTAssertThrowsError does, as in make an synchronous assertion that an asynchronous block will throw. It won't work quite that way because of how the code runs and returns.
The asynchronous code you refer to does not throw (timer initializer). As far as I know, there aren't any asynchronous blocks that can throw. Perhaps the question you should be asking is how can we make a synchronous operation choose to run synchronously sometimes, but also asynchronously when it feels like...
Alternatively for some additional complexity in every class you would like to test I've made a solution with what is almost bare minimum to make this easily testable and portable...
https://github.com/Jasperav/ThrowingAsyncError/pull/1/files
May I ask why you would ever want to do something like this?

Related

Why this test case hangs when run on a simulator?

Consider this simple test case
func test_Example() async {
let exp = expectation(description: "It's the expectation")
Task {
print("Task says: Hello!")
exp.fulfill()
}
wait(for: [exp], timeout: 600)
}
When I run this on an actual device, the test passes. However, when I run it on a simulator, it hangs and never completes.
Removing the async keyword from the function declaration solves the issue and the test passes on a simulator as well.
My actual use case / tests require the test to be async, so removing it is not really an option.
Solution: Change wait(for:) to await waitForExpectations(timeout:)
I don't know why it works, but probably it has something to do with the limited amount of threads available in a simulator.
waitForExpectations(timeout:) is marked with #MainActor and wait(for:) is not.
The mistake here doesn't really have anything to do with testing; it seems to have to do with what async/await is.
In your "simple test case", you have mistakenly added async to a method that is not async — that is, it doesn't make any await calls to an async method. This has nothing to do with tests, really; it's just a wrong thing to do in general, even though the compiler does not help you by warning you about it.
On the contrary, the chief purpose of a Task block is usually to let you call an async method in a context that is not async. That is why, when you take away the async designation, everything goes fine; you are behaving a lot more correctly.
However, the example still suffers from the problem that you aren't doing anything async even inside the Task. A more appropriate use of a Task would look more like this:
func test_Example() {
let exp = expectation(description: "It's the expectation")
Task {
try await Task.sleep(for: .seconds(3))
print("Task says: Hello!")
exp.fulfill()
}
wait(for: [exp], timeout: 4)
}
I have deliberately configured my numbers so that we are actually testing something here. This test passes, but if you change the 4 in the last line to 2, it fails — thus proving that we really are testing whether the Task finished in the given time, and failing if it doesn't.
If you're going to mark a test as async, then you would not use a Task inside it; you are already in a Task! But if we do that, then there is no need to wait for any expectations at all; the word await already waits:
func test_Example() async throws {
print("Task starts")
try await Task.sleep(for: .seconds(3))
print("Task says: Hello!") // three seconds later
}
But at this point we are no longer testing anything. And that's sort of the point; there isn't anything about your original async example that is async, so it remains unclear why we here in the first place.

Best practice with asynchronous functions Swift & Combine

I'm converting my Swift app to use Combine as well as async/await and I'm trying to understand what's the best way to handle interactions between asynchronous functions and the main thread.
Here's an asynchronous function that loads a user:
class AccountManager {
static func fetchOrLoadUser() async throws -> AppUser {
if let user = AppUser.current.value {
return user
}
let syncUser = try await loadUser()
let user = try AppUser(syncUser: syncUser)
AppUser.current.value = user // [warning]: "Publishing changes from background threads is not allowed"
return user
}
}
And a class:
class AppUser {
static var current = CurrentValueSubject<AppUser?,Never>(nil)
// ...
}
Note: I chose to use CurrentValueSubject because it allows me to both (1) read this value synchronously whenever I need it and (2) subscribe for changes.
Now, on the line marked above I get the error Publishing changes from background threads is not allowed, which I understand. I see different ways to solve this issue:
1. Mark whole AccountManager class as #MainActor
Since most of the work done in asynchronous functions is to wait for network results, I'm wondering if there is an issue with simply running everything on the main thread. Would that cause performance issues or not?
2. Englobe error line in DispatchQueue.main.sync
Is that a reasonable solution, or would that cause threading problems like deadlocks?
3. Use DispatchGroup with enter(), leave() and wait()
Like in this answer. Is there a difference at all with solution #2? Because this solution needs more lines of code so I'd rather not use it if possible —I prefer clean code.
You can wrap the call in an await MainActor.run { } block. I think this is the most Swifty way of doing that.
You should not use Dispatch mechanism while using Swift Concurrency, event though I think DispatchQueue.main.async { } is safe to use here.
The #MainActor attribute is safe but shouldn’t be used on anObservableObject and could potentially slow down the UI if CPU-bound code is run in a method of the annotated type.

Why does a Task within a #MainActor not block the UI?

Today I refactored a ViewModel for a SwiftUI view to structured concurrency. It fires a network request and when the request comes back, updates a #Published property to update the UI. Since I use a Task to perform the network request, I have to get back to the MainActor to update my property, and I was exploring different ways to do that. One straightforward way was to use MainActor.run inside my Task, which works just fine. I then tried to use #MainActor, and don't quite understand the behaviour here.
A bit simplified, my ViewModel would look somewhat like this:
class ContentViewModel: ObservableObject {
#Published var showLoadingIndicator = false
#MainActor func reload() {
showLoadingIndicator = true
Task {
try await doNetworkRequest()
showLoadingIndicator = false
}
}
#MainActor func someOtherMethod() {
// does UI work
}
}
I would have expected this to not work properly.
First, I expected SwiftUI to complain that showLoadingIndicator = false happens off the main thread. It didn't. So I put in a breakpoint, and it seems even the Task within a #MainActor is run on the main thread. Why that is is maybe a question for another day, I think I haven't quite figured out Task yet. For now, let's accept this.
So then I would have expected the UI to be blocked during my networkRequest - after all, it is run on the main thread. But this is not the case either. The network request runs, and the UI stays responsive during that. Even a call to another method on the main actor (e.g. someOtherMethod) works completely fine.
Even running something like Task.sleep() within doNetworkRequest will STILL work completely fine. This is great, but I would like to understand why.
My questions:
a) Am I right in assuming a Task within a MainActor does not block the UI? Why?
b) Is this a sensible approach, or can I run into trouble by using #MainActor for dispatching asynchronous work like this?
await is a yield point in Swift. It's where the current Task releases the queue and allows something else to run. So at this line:
try await doNetworkRequest()
your Task will let go of the main queue, and let something else be scheduled. It won't block the queue waiting for it to finish.
This means that after the await returns, it's possible that other code has been run by the main actor, so you can't trust the values of properties or other preconditions you've cached before the await.
Currently there's no simple, built-in way to say "block this actor until this finishes." Actors are reentrant.

Is it possible to throw a "RuntimeException" in Swift without declaring it?

I would like to throw an exception from some "deep" function, so it bubbles up to another function, where I want to catch it.
f1 calls f2 calls f3 calls ... fN which may throw an error
I would like to catch the error from f1.
I've read that in Swift I have to declare all methods with throws, and also call them using try.
But that's quite annoying:
enum MyErrorType : ErrorType {
case SomeError
}
func f1() {
do {
try f2()
} catch {
print("recovered")
}
}
func f2() throws {
try f3()
}
func f3() throws {
try f4()
}
...
func fN() throws {
if (someCondition) {
throw MyErrorType.SomeError
}
}
Isn't there a similar concept to the RuntimeException in Java, where throws doesn't leak all the way up the call chain?
Yes, it is possible!
Use: fatalError("your message here") to throw runtime exception
To elaborate on Максим Мартынов's answer, Swift has 3 ways to do throw undeclared, uncatchable errors (but other approaches are possible if you want to venture outside Swift's standard library). These are based on the 3 levels of optimization:
-Onone: No optimization; debug build
-O: Normal optimization; release build
-O SWIFT_DISABLE_SAFETY_CHECKS: Unchecked optimization; extremely optimized build
1. assertionFailure(_:)
Write this line when you're doing debugging tests and hit a line you don't think should ever be hit. These are removed in non-debug builds, so you must assume they will never be hit in the production app.
This has a sister function called assert(_:_:), which lets you assert at runtime whether a condition is true. assertionFailure(_:) is what you write when you know the situation is always bad, but don't think that'll harm the production code very much.
Usage:
if color.red > 0 {
assertionFailure("The UI should have guaranteed the red level stays at 0")
color = NSColor(red: 0, green: color.green, blue: color.blue)
}
2. preconditionFailure(_:)
Write this line when you're sure some condition you've described (in documentation, etc.) was not met. This works like assertionFailure(_:), but in release builds as well as debug ones.
Like assertionFailure(_:), this one's got a sister function called precondition(_:_:), which lets you decide at runtime whether a precondition was met. preconditionFailure(_:) is essentially that, but assuming the precondition is never met once the program gets to that line.
Usage:
guard index >= 0 else {
preconditionFailure("You passed a negative number as an array index")
return nil
}
Note that, in extremely optimized builds, it is not defined what happens if this line is hit! So if you don't want your app to wig out if it might ever hit this, then make sure the error state is handleable.
3. fatalError(_:)
Used as a last resort. When every other attempt to save the day has failed, here is your nuke. After printing the message you pass to it (along with the file and line number), the program stops dead in its tracks.
Once the program gets to this line, this line always runs, and the program never continues. This is true even in extremely optimized builds.
Usage:
#if arch(arm) || arch(arm64)
fatalError("This app cannot run on this processor")
#endif
Further reading: Swift Assertions by Andy Bargh
The error handling mechanism in Swift does not involve raising unchecked (runtime) exceptions. Instead, explicit error handling is required. Swift is certainly not the only recently designed language to go for this design – for instance Rust and Go also in their own ways also require explicitly describing the error paths in your code. In Objective-C the unchecked exception feature exists, but is largely used only for communicating programmer errors, with the notable exception of a few key Cocoa classes such as NSFileHandle which tends to catch people out.
Technically you do have the ability to raise Objective-C exceptions in Swift with the use of NSException(name: "SomeName", reason: "SomeReason", userInfo: nil).raise() as is explained in this excellent answer to this question, arguably a duplicate of your question. You really shouldn't raise NSExceptions though (not least because you have no Objective-C exception catching language feature available to you in Swift).
Why did they go with this design? Apple's "Error Handling in Swift 2.0" document explains the rationale clearly. Quoting from there:
This approach […] is very similar to the error handling model manually
implemented in Objective-C with the NSError convention. Notably, the
approach preserves these advantages of this convention:
Whether a method produces an error (or not) is an explicit part of its API contract.
Methods default to not producing errors unless they are explicitly marked.
The control flow within a function is still mostly explicit: a maintainer can tell exactly which statements can produce an error, and
a simple inspection reveals how the function reacts to the error.
Throwing an error provides similar performance to allocating an error and returning it – it isn’t an expensive, table-based stack
unwinding process. Cocoa APIs using standard NSError patterns can be
imported into this world automatically. Other common patterns (e.g.
CFError, errno) can be added to the model in future versions of Swift.
[…]
As to basic syntax, we decided to stick with the familiar language of
exception handling. […] by and large, error propagation in this
proposal works like it does in exception handling, and people are
inevitably going to make the connection.
Isn't there a similar concept to the RuntimeException in Java, where throws doesn't leak all the way up the call chain?
Swift does indeed have error handling which doesn't propegate at compile time.
However, before I discuss those, I must say that the one you point out, where you use the language's do...catch, try, throw, and throws keywords/features to handle errors, is by far the safest and most preferred. This ensures that every single time an error might be thrown or caught, it's handled correctly. This completely eliminates surprise errors, making all code more safe and predictable. Because of that inherent compile- and run-time safety, you should use this wherever you can.
func loadPreferences() throws -> Data {
return try Data(contentsOf: preferencesResourceUrl, options: [.mappedIfSafe, .uncached])
}
func start() {
do {
self.preferences = try loadPreferences()
}
catch {
print("Failed to load preferences", error)
assertionFailure()
}
}
guard let fileSizeInBytes = try? FileManager.default.attributesOfItem(atPath: path)[.size] as? Int64 else {
assertionFailure("Couldn't get file size")
return false
}
Probably the easiest way to silence Swift's compiler is with try! - this will allow you to use native Swift errors, but also ignore them.
Here's what your example code would look like with that:
enum MyErrorType : ErrorType {
case SomeError
}
func f1() {
f2()
}
func f2() {
f3()
}
func f3() {
try! f4()
}
...
func fN() throws {
if (someCondition) {
throw MyErrorType.SomeError
}
}
Obviously, this has the problem of not allowing you to ever catch these, so if you want a silent error you can catch, read on.
There are also assertions, preconditions, and fatalErrors, which I described in detail in my answer from October of 2017. The compiler provides reasonable handling of these, such as ensuring that return statements and other control flow are placed and omitted when appropriate. Like try!, however, these cannot be caught.
exit is in this family if your goal is to stop the program immediately.
If you venture outside Swift into the wider Apple ecosystem (that is, if you are writing Swift on an Apple platform), you also see Objective-C's NSException. As you desire, this can be thrown by Swift without using any language features guarding against that. Make sure you document that! However, this cannot be caught by Swift alone! You can write a thin Objective-C wrapper that lets you interact with it in the Swift world.
func silentButDeadly() {
// ... some operations ...
guard !shouldThrow else {
NSException.raise(NSExceptionName("Deadly and silent", format: "Could not handle %#", arguments: withVaList([problematicValue], {$0}))
return
}
// ... some operations ...
}
func devilMayCare() {
// ... some operations ...
silentButDeadly()
// ... some operations ...
}
func moreCautious() {
do {
try ObjC.catchException {
devilMayCare()
}
}
catch {
print("An NSException was thrown:", error)
assertionFailure()
}
}
Of course, if you're writing Swift in a Unix environment, you still have access to the terrifying world of Unix interrupts. You can use Grand Central Dispatch to both throw and catch these. And, as you desire, there's no way for the compiler to guard against them being thrown.
import Dispatch // or Foundation
signal(SIGINT, SIG_IGN) // // Make sure the signal does not terminate the application.
let sigintSource = DispatchSource.makeSignalSource(signal: SIGINT, queue: .main)
sigintSource.setEventHandler {
print("Got SIGINT")
// ...
exit(0)
}
sigintSource.resume()
exit is in this family if your goal is to trap it and read its code.

How to handle multiple network call in Alamofire

I need to call 2 apis in a view controller to fetch some data from server, I want them to start at the same time, but next step will only be triggered if both of them are returned(doesn't matter it's a success or failure).
I can come up with 2 solutions :
1. Chain them together. Call api1, call api2 in api1's result handler, wait for api2's result
2. Set 2 Bool indicator variables, create a check function, if both of these indicators are true, do next. In both Apis result handler, set corresponding indicator variable, then call check function to decide if it's good to go
First one is not sufficient enough, and I can't say the second one is a elegant solution. Does Alamofire has something like combine signal in Reactivecocoa? Or any better solution?
Your assessment is 100% correct. At the moment, the two options you laid out are really the only possible approaches. I agree with you that your second option is much better than the first given your use case.
If you wish to combine ReactiveCocoa with Alamofire, then that's certainly possible, but hasn't been done yet to my knowledge. You could also investigate whether PromiseKit would be able to offer some assistance, but it hasn't been glued together with Alamofire yet either. Trying to combine either of these libraries with the Alamofire response serializers will not be a trivial task by any means.
Switching gears a bit, I don't really think ReactiveCocoa or PromiseKit are very well suited for your use case since you aren't chaining service calls, you are running them in parallel. Additionally, you still need to run all your parsing logic and determine whether each one succeeded or failed and then update your application accordingly. What I'm getting at is that Option 2 is going to be your best bet by far unless you want to go to all the effort of combining PromiseKit or ReactiveCocoa with Alamofire's response serializers.
Here's what I would suggest to keep things less complicated.
import Foundation
import Alamofire
class ParallelServiceCaller {
var firstServiceCallComplete = false
var secondServiceCallComplete = false
func startServiceCalls() {
let firstRequest = Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["first": "request"])
firstRequest.responseString { request, response, dataString, error in
self.firstServiceCallComplete = true
self.handleServiceCallCompletion()
}
let secondRequest = Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["second": "request"])
secondRequest.responseString { request, response, dataString, error in
self.secondServiceCallComplete = true
self.handleServiceCallCompletion()
}
}
private func handleServiceCallCompletion() {
if self.firstServiceCallComplete && self.secondServiceCallComplete {
// Handle the fact that you're finished
}
}
}
The implementation is really clean and simple to follow. While I understand your desire to get rid of the completion flags and callback function, the other options such as ReactiveCocoa and/or PromiseKit are still going to have additional logic as well and may end up making things more complicated.
Another possible option is to use dispatch groups and semaphores, but that really adds complexity, but could get you much closer to a ReactiveCocoa or PromiseKit styled approach.
I hope that helps shed some light.
DispatchGroup would be a good option to handle multiple dependent requests in parallel
func loadData() {
let dispatchGroup = DispatchGroup()
func startRequests() {
dispatchGroup.enter()
loadDataRequest1()
dispatchGroup.enter()
loadDataRequest2()
dispatchGroup.notify(queue: .main) { [weak self] in
// Process your responses
}
loadDataRequest1() {
// Save your response
dispatchGroup.leave()
}
loadDataRequest2() {
// Save your response
dispatchGroup.leave()
}
}
startRequests()
}