Swift 3 adding #esaping attribute crashes when calling init in NSOperation - swift3

I have a function that has defined a closure like this:
func synchronizeData(completion externalCompletion: RequestsCompletionHandler?) {
let closure = {
(operationCompletion:#escaping ()->Void) in
assert(Thread.isMainThread, "must be the main thread")
/*
Internal (non-optional) completion handler
*/
let internalCompletion: RequestsCompletionHandler = {
(success, newData, decommissionRequired, errors) -> Void in
/*
Synchronization is finished. Firstly, call either the external completion handler or the delegate
*/
if let externalCompletion = externalCompletion {
externalCompletion(success, newData, decommissionRequired, errors)
}
else {
self.delegate?.synchroniationInteractor(self, didSynchronizeDataWithStatus: success, dataIsNew: newData, decommissionRequired: decommissionRequired, error: errors.last)
}
/*
Now call the closure operation's completion handler
*/
operationCompletion()
}
/*
The synchronization itself
*/
guard let _ = self.keychain.retrieveActivationIdentifiers() else {
internalCompletion(false, false, false, [NSError(domain: "", code: 0, userInfo: ["reason" : "unable to retrieve credentials"])])
return
}
var errors :[NSError] = []
var numberOfRequests = 0
var completedRequests = 0
var decommissionRequired: Bool?
/*
Synchronization results handler. Regardless of success for fail, increase completed requests counter and append any errors to array, if final request call main completion block
*/
let handleCompletedRequests = {
(error: NSError?) -> Void in
// assert(NSThread.isMainThread(), "must be the main thread")
if let error = error {
errors.append(error)
}
completedRequests += 1
if(completedRequests >= numberOfRequests) {
internalCompletion(errors.count == 0, true, decommissionRequired, errors)
/*
Decrement operations counter
*/
self.manageBusy(retain: false)
}
}
/*
Increment operations counter
*/
self.manageBusy(retain: true)
/*
Do the actual synchronization.
Fetch the Patient Data first
*/
self.fetchPatientDataInternal {
(success, newData, error) -> Void in
numberOfRequests = 6
//Fetch Patient Schedule
self.fetchPatientScheduleInternal {
(success, newData, error) -> Void in
handleCompletedRequests(error)
}
//Fetch Patient Thresholds
self.fetchPatientThresholdInternal {
(success, newData, error) -> Void in
handleCompletedRequests(error)
}
//Fetch Patient Device Settings
self.fetchPatientDeviceSettingsInternal {
(success, newData, decommissionReq, error) -> Void in
decommissionRequired = decommissionReq
handleCompletedRequests(error)
}
// Device Checkin
self.deviceCheckInInternal {
(success, newData, error) -> Void in
handleCompletedRequests(error)
}
// Upload Vitals
self.uploadPendingVitalsInternal {
(success, newData, error) -> Void in
handleCompletedRequests(error)
}
//Upload Health sessions
self.uploadPendingHealthSessionsInternal {
(success, newData, error) -> Void in
handleCompletedRequests(error)
}
}
}
let operation = CIAsyncronousOperation.init(closure: closure as! (()->Void)-> Void)
operation.name = "Data Synchronization"
isolationQueue.addOperation(operation)
}
When we invoke this line in the above function i.e
let operation = CIAsyncronousOperation.init(closure: closure as! (()->Void)-> Void)
The application crashes with the following info:
0x00000001003b083c CIAppliance`partial apply forwarder for CIAppliance.SynchronizationInteractor.(synchronizeData (completion : Swift.Optional<(Swift.Bool, Swift.Optional, Swift.Optional, Swift.Array<__ObjC.NSError>) -> ()>) -> ()).(closure #1) at SynchronizationInteractor.swift
The CIAsyncronousOperation init is defined as follows:
init(closure aClosure: #escaping (()->Void)-> Void)
{
closure = aClosure
}
I am unable to find out the reason for the crash. Is it casting issue or due to the new Swift 3 syntax change?

If you are having to force cast the function then there is a good chance you have got the function signature incorrect. If CIAsyncronousOperation is defined as follows it will compile. Both the function and the function that is parameter aClosure need to be set as #escaping
class CIAsyncronousOperation {
init(closure aClosure: #escaping (#escaping ()->Void)->Void)
{
closure = aClosure
}
var closure : (#escaping ()->Void)-> Void;
}

Related

How do I suppress output from multiple threads when running cargo test?

If I execute the below testcases with cargo test, the output of one_thread_test will be suppressed as stated in the documentation.
However the output from multi_thread_test will appear on stdout. Is it possible to match the behavior of single- and multi-threaded testcases?
#[test]
fn one_thread_test() {
println!("A");
println!("B");
}
#[test]
fn multi_thread_test() {
use std::thread;
let mut threads = vec![];
for _ in 0..100 {
let t = thread::spawn(move || {
println!("from thread");
});
threads.push(t);
}
for thread in threads {
thread.join().unwrap();
}
}
Here is a quick-and-dirty workaround.
It works by sending messages to a receiver owned by a struct in the main thread. The receiver prints all of the accumulated messages when it is dropped - this is important so that panics caused by failed assertions don't prevent the printing.
use std::sync::mpsc::{channel, Sender, Receiver};
struct TestPrinter {
receiver: Receiver<String>,
sender: Sender<String>,
}
impl TestPrinter {
fn new() -> TestPrinter {
let (sender, receiver) = channel();
TestPrinter { receiver, sender }
}
fn sender(&self) -> Sender<String> {
self.sender.clone()
}
}
impl Drop for TestPrinter {
fn drop(&mut self) {
while let Some(v) = self.receiver.try_recv().ok() {
println!("later: {}", v);
}
}
}
And a convenience macro so it feels mostly like calling println!:
macro_rules! myprint {
($send: expr, $($arg:tt)*) => {
(*&$send).send(format!($($arg)*));
};
}
In order to send messages for printing, you have get a sender for each thread:
#[test]
fn multi_thread_test() {
use std::thread;
let mut threads = vec![];
let printer = TestPrinter::new();
for _ in 0..100 {
let sender = printer.sender();
let t = thread::spawn(move || {
myprint!(sender, "from thread");
});
threads.push(t);
}
for thread in threads {
thread.join().unwrap();
}
}
The actual printing happens when printer goes out of scope. It's in the main thread so it won't print during successful tests unless --nocapture is specified.

Migrating Alamofire.Request extension from Swift 2 to Swift 3

I'm trying to migrate an extension to Alamofire.Request but am getting the error Cannot call value of non-function type 'HTTPURLResponse?'.
I know the compiler thinks I'm referring to the member response and not the function.
I've already replaced Request.JSONResponseSerializer with DataRequest.jsonResponseSerializer.
Can anyone see what I'm missing?
extension Alamofire.Request {
public func responseSwiftyJSON(_ queue: DispatchQueue? = nil, options: JSONSerialization.ReadingOptions = .allowFragments, completionHandler: #escaping (NSURLRequest, HTTPURLResponse?, SwiftyJSON.JSON, Error?) -> Void) -> Self {
return response(responseSerializer: Alamofire.DataRequest.jsonResponseSerializer(options: options)) { response in
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
var responseJSON: JSON
if response.result.isFailure {
responseJSON = JSON.null
} else {
responseJSON = SwiftyJSON.JSON(response.result.value!)
}
dispatch_async(queue ?? dispatch_get_main_queue(), {
completionHandler(self.request!, self.response, responseJSON, response.result.error)
})
}
}
}
}
I'm not sure if this is compatible with older versions, but I suggest you rewrite your extensions as this:
extension DataRequest {
public func responseSwiftyJSON(queue: DispatchQueue? = nil, options: JSONSerialization.ReadingOptions = .allowFragments, completionHandler: #escaping (URLRequest?, HTTPURLResponse?, SwiftyJSON.JSON, Error?) -> Void) -> Self {
return responseJSON(queue: queue, options: options) {
let responseJSON: JSON
switch result {
case let .success(json): responseJSON = JSON(json)
case .failure: responseJSON = JSON.null
}
completionHandler(request, response, responseJSON, error)
}
}
}

'self' used before super.init call in swift 3.0

I have sub class of OutputStream. In that I have two init() methods one have prefix word convenience.
Here is my code :
class FileOutputStream : OutputStream
{
fileprivate let filepath:URL
fileprivate let channel:DispatchIO!
convenience init?(filename:String) {
let pathURL = FileManager.default.urls(for: FileManager.SearchPathDirectory.documentDirectory, in:.userDomainMask).first!.appendingPathComponent(filename)
self.init(filepath:pathURL)
}
init?(filepath f:URL) {
self.filepath = f
//if let path = f.path,
if let cpath = (f.path).cString(using: String.Encoding.utf8) {
let outputflag:Int32 = O_CREAT | O_WRONLY // create, write-only
let mode:mode_t = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH // permissions: u+rw, g+r, o+r
self.channel = DispatchIO(type: DispatchIO.StreamType.stream, path:cpath, oflag:outputflag,mode: mode, queue: DispatchQueue.global(qos: DispatchQoS.QoSClass.background)) { (errcode:Int32) -> Void in
if errcode != 0 {
print("FileOutputStream: error creating io channel")
}
}
}
else {
self.channel = nil
return nil
}
}
func write(_ string: String) {
if let dataString = string.data(using: String.Encoding.utf8) {
dataString.withUnsafeBytes {(bytes: UnsafePointer<UInt8>) -> Void in
var data = DispatchData.empty
data.append(bytes, count: dataString.count)
self.channel.write(offset: 0, data: data, queue: DispatchQueue.global(qos:.background), ioHandler: { (complete: Bool, data: DispatchData?, errorCode: Int32) in
//handle progress reporting here
if errorCode != 0 {
print("FileOutputStream: error writing data to channel")
}
})
}
}
}
deinit {
self.channel.close(flags: DispatchIO.CloseFlags.stop)
}
}
I am getting error
'self' used before super.init call
I have tried by writing
super.init()
in both method, at start of methods
at end of methods
only in second method
only in first method.
But still getting error. If anyone know, please help me.
Use super.init(url: f, append: false/true) at the end of your init?(filepath f:URL) method .
Your intializer must call designated intializer of super class..Call any of these init methods that is available in OutputStream class...
public init(toMemory: ())
public init(toBuffer buffer: UnsafeMutablePointer<UInt8>, capacity: Int)
#available(iOS 4.0, *)
public init?(url: URL, append shouldAppend: Bool)

Cannot assign value of type '(UIViewController?, NSError?) -> Void' to type '((UIViewController?, Error?) -> Void)?'

i have this function to authenticate the player and it worked just fine until xcode8 beta 6:
func authenticateLocalPlayer()
{
print(#function)
// WillSignIn
self.delegate?.willSignIn?()
// The player authenticates in an asynchronous way, so we need a notification to inform when the authentication was completed successfully
// If the local player is already connected, return and notificate
if GameKitHelper.sharedGameKitHelper.localPlayer.isAuthenticated {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: Constants.LocalPlayerIsAuthenticated), object: nil)
return
}
// Calling the authentication view controller
self.localPlayer.authenticateHandler = {(viewController: UIViewController?, error: NSError?) -> Void in
self.addLastError(error: error)
if (viewController != nil)
{
self.addAuthenticationViewController(authenticationViewController: viewController!)
}
// If the localPlayer authenticated successfully notificate
else if (self.localPlayer.isAuthenticated == true)
{
self.gameCenterConnected = true
print("Local player ID: \(self.localPlayer.playerID)")
print("Local player Alias: \(self.localPlayer.alias)")
NotificationCenter.default.post(name: NSNotification.Name(rawValue: Constants.LocalPlayerIsAuthenticated), object: nil)
}
// If the localPlayer failed to authenticate
else
{
self.gameCenterConnected = false
}
if (error != nil)
{
// Handle error here
}
}
}
now i get this error "Cannot assign value of type '(UIViewController?, NSError?) -> Void' to type '((UIViewController?, Error?) -> Void)?' "
in this line
self.localPlayer.authenticateHandler = {(viewController: UIViewController?, error: NSError?) -> Void in
self.addLastError(error: error)
if (viewController != nil)
and i have no idea what is going on.
can anyone help me please?
sorry, the answer was as simple as only had to change this line:
self.localPlayer.authenticateHandler = {(viewController: UIViewController?, error: NSError?) -> Void in
to
self.localPlayer.authenticateHandler = {(viewController: UIViewController?, error: Error?) -> Void in
my bad! sorry!

Testing assertion in Swift

I'm writing unit tests for a method that has an assertion. The Swift Language guide recommends using assertions for "invalid conditions":
Assertions cause your app to terminate and are not a substitute for
designing your code in such a way that invalid conditions are unlikely
to arise. Nonetheless, in situations where invalid conditions are
possible, an assertion is an effective way to ensure that such
conditions are highlighted and noticed during development, before your
app is published.
I want to test the failure case.
However, there is not XCTAssertThrows in Swift (as of Beta 6). How can I write an unit test that tests that an assertion fails?
Edit
As per #RobNapier's suggestion, I tried wrapping XCTAssertThrows in an Objective-C method and calling this method from Swift. This doesn't work as the macro does not catch the fatal error caused by assert, and thus the test crashes.
assert and its sibling precondition don't throw exceptions cannot be "caught" (even with Swift 2's error handling).
A trick you can use is to write your own drop-in replacement that does the same thing but can be replaced for tests. (If you're worried about performance, just #ifdef it away for release builds.)
custom precondition
/// Our custom drop-in replacement `precondition`.
///
/// This will call Swift's `precondition` by default (and terminate the program).
/// But it can be changed at runtime to be tested instead of terminating.
func precondition(#autoclosure condition: () -> Bool, #autoclosure _ message: () -> String = "", file: StaticString = __FILE__, line: UWord = __LINE__) {
preconditionClosure(condition(), message(), file, line)
}
/// The actual function called by our custom `precondition`.
var preconditionClosure: (Bool, String, StaticString, UWord) -> () = defaultPreconditionClosure
let defaultPreconditionClosure = {Swift.precondition($0, $1, file: $2, line: $3)}
test helper
import XCTest
extension XCTestCase {
func expectingPreconditionFailure(expectedMessage: String, #noescape block: () -> ()) {
let expectation = expectationWithDescription("failing precondition")
// Overwrite `precondition` with something that doesn't terminate but verifies it happened.
preconditionClosure = {
(condition, message, file, line) in
if !condition {
expectation.fulfill()
XCTAssertEqual(message, expectedMessage, "precondition message didn't match", file: file.stringValue, line: line)
}
}
// Call code.
block();
// Verify precondition "failed".
waitForExpectationsWithTimeout(0.0, handler: nil)
// Reset precondition.
preconditionClosure = defaultPreconditionClosure
}
}
example
func doSomething() {
precondition(false, "just not true")
}
class TestCase: XCTestCase {
func testExpectPreconditionFailure() {
expectingPreconditionFailure("just not true") {
doSomething();
}
}
}
(gist)
Similar code will work for assert, of course. However, since you're testing the behavior, you obviously want it to be part of your interface contract. You don't want optimized code to violate it, and assert will be optimized away. So better use precondition here.
Agree with nschum's comment that it doesn't seem right to unit test assert because by default it wont be in the prod code. But if you really wanted to do it, here is the assert version for reference:
override assert
func assert(#autoclosure condition: () -> Bool, #autoclosure _ message: () -> String = "", file: StaticString = __FILE__, line: UInt = __LINE__) {
assertClosure(condition(), message(), file, line)
}
var assertClosure: (Bool, String, StaticString, UInt) -> () = defaultAssertClosure
let defaultAssertClosure = {Swift.assert($0, $1, file: $2, line: $3)}
helper extension
extension XCTestCase {
func expectAssertFail(expectedMessage: String, testcase: () -> Void) {
// arrange
var wasCalled = false
var assertionCondition: Bool? = nil
var assertionMessage: String? = nil
assertClosure = { condition, message, _, _ in
assertionCondition = condition
assertionMessage = message
wasCalled = true
}
// act
testcase()
// assert
XCTAssertTrue(wasCalled, "assert() was never called")
XCTAssertFalse(assertionCondition!, "Expected false to be passed to the assert")
XCTAssertEqual(assertionMessage, expectedMessage)
// clean up
assertClosure = defaultAssertClosure
}
}
Thanks to nschum and Ken Ko for the idea behind this answer.
Here is a gist for how to do it
Here is an example project
This answer is not just for assert. It's also for the other assertion methods (assert, assertionFailure, precondition, preconditionFailure and fatalError)
1. Drop ProgrammerAssertions.swift to the target of your app or framework under test. Just besides your source code.
ProgrammerAssertions.swift
import Foundation
/// drop-in replacements
public func assert(#autoclosure condition: () -> Bool, #autoclosure _ message: () -> String = "", file: StaticString = __FILE__, line: UInt = __LINE__) {
Assertions.assertClosure(condition(), message(), file, line)
}
public func assertionFailure(#autoclosure message: () -> String = "", file: StaticString = __FILE__, line: UInt = __LINE__) {
Assertions.assertionFailureClosure(message(), file, line)
}
public func precondition(#autoclosure condition: () -> Bool, #autoclosure _ message: () -> String = "", file: StaticString = __FILE__, line: UInt = __LINE__) {
Assertions.preconditionClosure(condition(), message(), file, line)
}
#noreturn public func preconditionFailure(#autoclosure message: () -> String = "", file: StaticString = __FILE__, line: UInt = __LINE__) {
Assertions.preconditionFailureClosure(message(), file, line)
runForever()
}
#noreturn public func fatalError(#autoclosure message: () -> String = "", file: StaticString = __FILE__, line: UInt = __LINE__) {
Assertions.fatalErrorClosure(message(), file, line)
runForever()
}
/// Stores custom assertions closures, by default it points to Swift functions. But test target can override them.
public class Assertions {
public static var assertClosure = swiftAssertClosure
public static var assertionFailureClosure = swiftAssertionFailureClosure
public static var preconditionClosure = swiftPreconditionClosure
public static var preconditionFailureClosure = swiftPreconditionFailureClosure
public static var fatalErrorClosure = swiftFatalErrorClosure
public static let swiftAssertClosure = { Swift.assert($0, $1, file: $2, line: $3) }
public static let swiftAssertionFailureClosure = { Swift.assertionFailure($0, file: $1, line: $2) }
public static let swiftPreconditionClosure = { Swift.precondition($0, $1, file: $2, line: $3) }
public static let swiftPreconditionFailureClosure = { Swift.preconditionFailure($0, file: $1, line: $2) }
public static let swiftFatalErrorClosure = { Swift.fatalError($0, file: $1, line: $2) }
}
/// This is a `noreturn` function that runs forever and doesn't return.
/// Used by assertions with `#noreturn`.
#noreturn private func runForever() {
repeat {
NSRunLoop.currentRunLoop().run()
} while (true)
}
2. Drop XCTestCase+ProgrammerAssertions.swift to your test target. Just besides your test cases.
XCTestCase+ProgrammerAssertions.swift
import Foundation
import XCTest
#testable import Assertions
private let noReturnFailureWaitTime = 0.1
public extension XCTestCase {
/**
Expects an `assert` to be called with a false condition.
If `assert` not called or the assert's condition is true, the test case will fail.
- parameter expectedMessage: The expected message to be asserted to the one passed to the `assert`. If nil, then ignored.
- parameter file: The file name that called the method.
- parameter line: The line number that called the method.
- parameter testCase: The test case to be executed that expected to fire the assertion method.
*/
public func expectAssert(
expectedMessage: String? = nil,
file: StaticString = __FILE__,
line: UInt = __LINE__,
testCase: () -> Void
) {
expectAssertionReturnFunction("assert", file: file, line: line, function: { (caller) -> () in
Assertions.assertClosure = { condition, message, _, _ in
caller(condition, message)
}
}, expectedMessage: expectedMessage, testCase: testCase) { () -> () in
Assertions.assertClosure = Assertions.swiftAssertClosure
}
}
/**
Expects an `assertionFailure` to be called.
If `assertionFailure` not called, the test case will fail.
- parameter expectedMessage: The expected message to be asserted to the one passed to the `assertionFailure`. If nil, then ignored.
- parameter file: The file name that called the method.
- parameter line: The line number that called the method.
- parameter testCase: The test case to be executed that expected to fire the assertion method.
*/
public func expectAssertionFailure(
expectedMessage: String? = nil,
file: StaticString = __FILE__,
line: UInt = __LINE__,
testCase: () -> Void
) {
expectAssertionReturnFunction("assertionFailure", file: file, line: line, function: { (caller) -> () in
Assertions.assertionFailureClosure = { message, _, _ in
caller(false, message)
}
}, expectedMessage: expectedMessage, testCase: testCase) { () -> () in
Assertions.assertionFailureClosure = Assertions.swiftAssertionFailureClosure
}
}
/**
Expects an `precondition` to be called with a false condition.
If `precondition` not called or the precondition's condition is true, the test case will fail.
- parameter expectedMessage: The expected message to be asserted to the one passed to the `precondition`. If nil, then ignored.
- parameter file: The file name that called the method.
- parameter line: The line number that called the method.
- parameter testCase: The test case to be executed that expected to fire the assertion method.
*/
public func expectPrecondition(
expectedMessage: String? = nil,
file: StaticString = __FILE__,
line: UInt = __LINE__,
testCase: () -> Void
) {
expectAssertionReturnFunction("precondition", file: file, line: line, function: { (caller) -> () in
Assertions.preconditionClosure = { condition, message, _, _ in
caller(condition, message)
}
}, expectedMessage: expectedMessage, testCase: testCase) { () -> () in
Assertions.preconditionClosure = Assertions.swiftPreconditionClosure
}
}
/**
Expects an `preconditionFailure` to be called.
If `preconditionFailure` not called, the test case will fail.
- parameter expectedMessage: The expected message to be asserted to the one passed to the `preconditionFailure`. If nil, then ignored.
- parameter file: The file name that called the method.
- parameter line: The line number that called the method.
- parameter testCase: The test case to be executed that expected to fire the assertion method.
*/
public func expectPreconditionFailure(
expectedMessage: String? = nil,
file: StaticString = __FILE__,
line: UInt = __LINE__,
testCase: () -> Void
) {
expectAssertionNoReturnFunction("preconditionFailure", file: file, line: line, function: { (caller) -> () in
Assertions.preconditionFailureClosure = { message, _, _ in
caller(message)
}
}, expectedMessage: expectedMessage, testCase: testCase) { () -> () in
Assertions.preconditionFailureClosure = Assertions.swiftPreconditionFailureClosure
}
}
/**
Expects an `fatalError` to be called.
If `fatalError` not called, the test case will fail.
- parameter expectedMessage: The expected message to be asserted to the one passed to the `fatalError`. If nil, then ignored.
- parameter file: The file name that called the method.
- parameter line: The line number that called the method.
- parameter testCase: The test case to be executed that expected to fire the assertion method.
*/
public func expectFatalError(
expectedMessage: String? = nil,
file: StaticString = __FILE__,
line: UInt = __LINE__,
testCase: () -> Void) {
expectAssertionNoReturnFunction("fatalError", file: file, line: line, function: { (caller) -> () in
Assertions.fatalErrorClosure = { message, _, _ in
caller(message)
}
}, expectedMessage: expectedMessage, testCase: testCase) { () -> () in
Assertions.fatalErrorClosure = Assertions.swiftFatalErrorClosure
}
}
// MARK:- Private Methods
private func expectAssertionReturnFunction(
functionName: String,
file: StaticString,
line: UInt,
function: (caller: (Bool, String) -> Void) -> Void,
expectedMessage: String? = nil,
testCase: () -> Void,
cleanUp: () -> ()
) {
let expectation = expectationWithDescription(functionName + "-Expectation")
var assertion: (condition: Bool, message: String)? = nil
function { (condition, message) -> Void in
assertion = (condition, message)
expectation.fulfill()
}
// perform on the same thread since it will return
testCase()
waitForExpectationsWithTimeout(0) { _ in
defer {
// clean up
cleanUp()
}
guard let assertion = assertion else {
XCTFail(functionName + " is expected to be called.", file: file.stringValue, line: line)
return
}
XCTAssertFalse(assertion.condition, functionName + " condition expected to be false", file: file.stringValue, line: line)
if let expectedMessage = expectedMessage {
// assert only if not nil
XCTAssertEqual(assertion.message, expectedMessage, functionName + " called with incorrect message.", file: file.stringValue, line: line)
}
}
}
private func expectAssertionNoReturnFunction(
functionName: String,
file: StaticString,
line: UInt,
function: (caller: (String) -> Void) -> Void,
expectedMessage: String? = nil,
testCase: () -> Void,
cleanUp: () -> ()
) {
let expectation = expectationWithDescription(functionName + "-Expectation")
var assertionMessage: String? = nil
function { (message) -> Void in
assertionMessage = message
expectation.fulfill()
}
// act, perform on separate thead because a call to function runs forever
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), testCase)
waitForExpectationsWithTimeout(noReturnFailureWaitTime) { _ in
defer {
// clean up
cleanUp()
}
guard let assertionMessage = assertionMessage else {
XCTFail(functionName + " is expected to be called.", file: file.stringValue, line: line)
return
}
if let expectedMessage = expectedMessage {
// assert only if not nil
XCTAssertEqual(assertionMessage, expectedMessage, functionName + " called with incorrect message.", file: file.stringValue, line: line)
}
}
}
}
3. Use assert, assertionFailure, precondition, preconditionFailure and fatalError normally as you always do.
For example: If you have a function that does a division like the following:
func divideFatalError(x: Float, by y: Float) -> Float {
guard y != 0 else {
fatalError("Zero division")
}
return x / y
}
4. Unit test them with the new methods expectAssert, expectAssertionFailure, expectPrecondition, expectPreconditionFailure and expectFatalError.
You can test the 0 division with the following code.
func testFatalCorrectMessage() {
expectFatalError("Zero division") {
divideFatalError(1, by: 0)
}
}
Or if you don't want to test the message, you simply do.
func testFatalErrorNoMessage() {
expectFatalError() {
divideFatalError(1, by: 0)
}
}
Matt Gallagher's CwlPreconditionTesting project on github adds a catchBadInstruction function which gives you the ability to test for assertion/precondition failures in unit test code.
The CwlCatchBadInstructionTests file shows a simple illustration of its use. (Note that it only works in the simulator for iOS.)
I believe as of Beta6 it is still impossible for Swift to catch an exception directly. The only way you can handle this is to write that particular test case in ObjC.
That said, note that _XCTAssertionType.Throws does exist, which suggests that the Swift team is aware of this and intends eventually to provide a solution. It is quite imaginable that you could write this assertion yourself in ObjC and expose it to Swift (I can't think of any reason that would be impossible in Beta6). The one big problem is that you may not easily be able to get good location information out of it (the specific line that failed, for instance).
We have Swift (4) code that tests an Objective-C framework. Some of the framework methods call into NSAssert.
Inspired by NSHipster, I ended up with an implementation like such:
SwiftAssertionHandler.h (use this in a bridging header)
#interface SwiftAssertionHandler : NSAssertionHandler
#property (nonatomic, copy, nullable) void (^handler)(void);
#end
SwiftAssertionHandler.m
#implementation SwiftAssertionHandler
- (instancetype)init {
if (self = [super init]) {
[[[NSThread currentThread] threadDictionary] setValue:self
forKey:NSAssertionHandlerKey];
}
return self;
}
- (void)dealloc {
[[[NSThread currentThread] threadDictionary] removeObjectForKey:NSAssertionHandlerKey];
}
- (void)handleFailureInMethod:(SEL)selector object:(id)object file:(NSString *)fileName lineNumber:(NSInteger)line description:(NSString *)format, ... {
if (self.handler) {
self.handler();
}
}
- (void)handleFailureInFunction:(NSString *)functionName file:(NSString *)fileName lineNumber:(NSInteger)line description:(NSString *)format, ... {
if (self.handler) {
self.handler();
}
}
#end
Test.swift
let assertionHandler = SwiftAssertionHandler()
assertionHandler.handler = { () -> () in
// i.e. count number of assert
}