Let's start by running some equivalent code in Objective-C so that we understand what Cocoa does under the hood here:
NSDictionary* d = @{@"Howdy": [NSObject new]};
NSError* err;
[NSJSONSerialization dataWithJSONObject:d options:0 error:&err];
NSLog(@"%@", err);
If this were the sort of thing that results in an NSError, we would be able to read the value of err
and print it to the console. But it isn't, and we can't. We never got to the NSLog statement. Instead, the program crashes unceremoniously:
Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: 'Invalid type in JSON write (NSObject)'
1 libobjc.A.dylib 0x00007fff50b97b20 objc_exception_throw + 48
2 Foundation 0x00007fff2580cb68 -[_NSJSONWriter writeRootObject:toStream:options:error:] + 0
3 Foundation 0x00007fff2580ff03 ___writeJSONObject_block_invoke + 371
[and so on]
That is not an NSError. It is an NSException. They are completely different things, and basically it means our program crashes. It is not a throw-and-catch of an error; it is sudden death.
That is what the documentation warned us would happen. This method, dataWithJSONObject:options:error
, does not fail in good order if we supply an object that cannot be turned into JSON. It crashes instead:
If obj
will not produce valid JSON, an exception is thrown. This exception is thrown prior to parsing and represents a programming error, not an internal error. You should check whether the input will produce valid JSON before calling this method by using isValidJSONObject:
.
(Italics mine.)
So the correct way to discover the issue and prevent the crash was to call isValidJSONObject:
first. This method, dataWithJSONObject:options:error
, will not catch this sort of problem and return a mere NSError; it will kill the whole program.
And that is what happened to you. Your call to try JSONSerialization.data(withJSONObject:)
did not throw
. It just crashed. You can see that if you try it by itself in an actual Swift app:
override func viewDidLoad() {
super.viewDidLoad()
do {
try testCreateJSONFrominValidDictionaryThrows()
} catch {
print("oops")
}
}
func createBodyDataFrom(dictionary: [String: Any]) throws -> Data {
let bodyData = try JSONSerialization.data(withJSONObject: dictionary, options: [])
return bodyData
}
func testCreateJSONFrominValidDictionaryThrows() throws {
let validDictionary: [String: Any] = [
"object": NSObject()
]
try createBodyDataFrom(dictionary: validDictionary)
}
We run the app, and what happens? We do not print "oops". Instead, we get exactly the same crash as in Objective-C.
And that is what happens to your case as well. We never threw because we crashed instead. And the test failure reports that fact. Your testClient
crashed out from under you (you might be able to detect this with some twiddling of the Console), and the test itself came back as a failure because we never threw an NSError.
The difference between Cocoa throwing an NSException and returning an NSError is crucial here. Only the latter counts for Swift as throwing an error; the former is a crash. See for example
How can I throw an NSError from a Swift class and catch it in an Objective-C class?
Avoid handling all exceptions until they've reached a global handler
How to catch NSUnknownKeyException in swift 2.2?