Monday, July 20, 2015

Swift 2 and Unit Testing

One of the many new features with Xcode 7 and Swift 2 is the @testable attribute.  While we have been able to do unit testing in previous versions of Xcode and Swift the big drawback has always been that any routine we wanted to test had to have an access level of public.  This was a pretty big drawback especially with frameworks where we need to test routines without making them public.  This has changed in Xcode 7 and Swift 2.  In this post we will demonstrate how to use the new @testable attribute in our test source code to make all public and internal routines usable by our test code but not usable by other frameworks and app targets.

We will start off by creating a new project called Testability.  When Xcode creates this project it will create a module that is also named Testability.  The following image shows the Testability project that I created.


Now that we have our project, lets create a struct that we can test.  Lets name this struct TextValidation and put the following code in it:

struct TextValidation {
    let regExMatchingString = "^[\\s?[a-zA-Z0-9\\-]]{0,5}$"
   
    func validateString(str: String) -> Bool {
        if let _ = str.rangeOfString(regExMatchingString, options: .RegularExpressionSearch) {
            return true
        } else {
            return false
        }
    }
}
This TextValidation struct sets a constant name regExMatchingString to a regular expression string.  This regular expression is then used in the validateString method to validate the string that is passed into the method.  If the string matches the regular expresion, the method returns true otherwise it returns false.

Now lets create our test class.  Right click on the TestabilityTests module and select the New File option.




In the menu that pops up, select the Unit Test Case Class option.  This will create a standard unit test class with the basic code that we need to get started.  You can name the class anything you wish but I like to name my test classes with a name that describes what types of tests are in them.  In this case I named my class TextValidationTests.

When Xcode creates the new unit test class it creates it with four functions.  These are:


  • setUp():  This method is called before each test is run.  If we have four tests defined in our class and we ran all four tests then this setUp() method will be called four times (once before each test).
  • tearDown():  This method is called after each test is run.  If we have four tests defined in our class and we ran all four tests then this tearDown() method will be called four times (once after each test is run).
  • testExample():  Sample test method, no code is actually in this method.
  • testPerformanceExample():  Sample performance test method.

The last two methods (testExample() and  testPerformanceExample() ) can be safely deleted.
Now we need to use the @testable attribute to import the module that we wish to test.  In our case the module is the Testability module.  Add the following line right below the Import XCTest line.

   @testable import Testability

Now lets create a couple of tests.  Add the following two method below the tearDown() method.

     func testAgencyTrue() {
        let validation = TextValidation()
        XCTAssertTrue(validation.validateString("hel-o"))
    }
   
    func testAgencyFalse() {
        let validation = TextValidation()
        XCTAssertFalse(validation.validateString("hel-o6"))
    }
Now we can run the test by clicking on the little triangle to the left of the class definition.




If everything tested correctly we should see little green check boxes where the triangles use to be.
In this example we used the XCTAssertTrue() and XCTAssertFalse() methods for our tests but there are a number of other XCTAssert methods that can also be used.  Here is a list of some of the most useful tests:

Equality Tests:
XCTAssertEqual():  Generates a failure when two expressions are not equal.
XCTAssertNotEqual():  Generates a failure when two expressions are equal.
XCTAssertGreaterThan():  Generates a failure when the first expression is less than or equal to the second.
XCTAssertGreaterThanOrEqual():  Generates a failure when the first expression is less than the second.
XCTAssertLessThan():  Generates a failure when the first expression is greater than or equal to the second.
XCTAssertLessThanOrEqual():  Generates a failure when the first expression is greater than the second.

Nil Tests
XCTAssertNil():  Generates a failure when the expression is not nil.
XCTAssertNotNil():  Generates a failure when the expression is nil.

Boolean Tests
XCTAssertTrue():  Generates a failure when the expression evaluates to false.
XCTAssertFalse():  Generates a failure when the expression evaluates to true.


Unit testing can be incredible useful for projects that are going though continuous updates.  With the new @testable attribute, Apple has eliminated the biggest hurdle that we had for adopting unit testing in our projects.

Friday, July 3, 2015

RSNetworking 2

I would like to announce the release of RSNetworking 2 for Swift 2.  RSNetworking 2 has all the same great features that RSNetworking has but it was written to work with Swift 2.  RSNetworking and RSNetworking 2 are network libraries written entirely in the Swift programming language.  You can find RSNetworking 2 here:  https://github.com/hoffmanjon/RSNetworking2

Below lists the API that is exposed with RSNetworking 2

Classes
This section lists the classes that make up the RSNetworking API

RSTransaction
RSTransaction is the class that defines the transaction we wish to make. It exposes four properties, one initiator and one method.

Properties
  •   TransactionType - This defines the HTTP request method. Currently there are three types, GET, POST, UNKNOWN. Only the GET and POST actually sends a request.
  •   baseURL - This is the base URL to use for the request. This will normally look something like this: "https://itunes.apple.com". If you are going to a non-standard port you would put that here as well. It will look something like this: "http://mytestserver:8080"
  •   path - The path that will be added to the base url. This will normally be something like this: "search". It can also include a longer path string like: "path/to/my/service"
  •   parameters - Any parameters to send to the service.

Initiators
  •   init(transactionType: RSTransactionType, baseURL: String, path: String, parameters: [String: String]) - This will initialize the RSTransaction with all properties needed.

Functions
  •   getFullURLString() -> String - Builds and returns the full URL needed to connect to the service.

RSTransactionRequest
RSTransactionRequest is the class that builds and sends out the request to the service defined by the RSTransaction. It exposes four functions.

Functions
  •   dataFromRSTransaction(transaction: RSTransaction, completionHandler handler: RSNetworking.dataFromRSTransactionCompletionCompletionClosure): Retrieves an NSData object from the service defined by the RSTransaction. This is the main function and is used by the other three functions to retrieve an NSData object prior to converting it to the required format.
  •   stringFromRSTransaction(transaction: RSTransaction, completionHandler handler: RSNetworking.stringFromRSTransactionCompletionCompletionClosure): Retrieves an NSString object from the service defined by the RSTransaction. This function uses the dataFromRSTransaction function to retrieve an NSData object and then converts it to an NSString object.
  •   dictionaryFromRSTransaction(transaction: RSTransaction, completionHandler handler: RSNetworking.dictionaryFromRSTransactionCompletionCompletionClosure): Retrieves an NSDictionary object from the service defined by the RSTransaction. This function uses the dataFromRSTransaction function to retrieve an NSData object and then converts it to an NSDictionary object. The data returned from the URL should be in JSON format for this function to work properly.
  •   imageFromRSTransaction(transaction: RSTransaction, completionHandler handler: RSNetworking.imageFromRSTransactionCompletionCompletionClosure): Retrieves an UIImage object from the service defined by the RSTransaction. This function uses the dataFromRSTransaction function to retrieve an NSData object and then converts it to an UIImage object.

RSURLRequest
RSURLRequest will send a GET request to a service with just a URL. There is no need to define a RSTransaction to use this class. RSURLRequest exposes four functions.

Functions
  •   dataFromURL(url: NSURL, completionHandler handler: RSNetworking.dataFromURLCompletionClosure): Retrieves an NSData object from the URL passed in. This is the main function and is used by the other three functions to retrieve an NSData object prior to converting it to the required format
  •   stringFromURL(url: NSURL, completionHandler handler: RSNetworking.stringFromURLCompletionClosure): Retrieves an NSString object from the URL passed in. This function uses the dataFromURL function to retrieve an NSData object and then converts it to an NSString object.
  •   dictionaryFromJsonURL(url: NSURL, completionHandler handler: RSNetworking.dictionaryFromURLCompletionClosure): Retrieves an NSDictionary object from the URL passed in. This function uses the dataFromURL function to retrieve an NSData object and then converts it to an NSDictionary object. The data returned from the URL should be in JSON format for this function to work properly.
  •   imageFromURL(url: NSURL, completionHandler handler: RSNetworking.imageFromURLCompletionClosure): Retrieves an UIImage object from the URL. This function uses the dataFromURL function to retrieve an NSData object and then converts it to an UIImage object.

RSUtilities
RSUtilities will contain various utilities that do not have their own class. Currently there is only one function exposed by this class

Functions
  •   isNetworkAvailable(hostname: NSString) -> Bool - This function will check to see if the network is available. This is a class function.
  •   networkConnectionType(hostname: NSString) -> ConnectionType - This function will return the type of network connection that is available. The ConnectionType is an enum which can equal one of the following three types: NONETWORK, MOBILE3GNETWORK or WIFINETWORK.

Extensions
This section lists the extensions that RSNetworking adds to the Swift language

UIImageView
  •     setImageForURL(url: NSString, placeHolder: UIImage): Sets the image in the UIImageView to the placeHolder image and then asynchronously downloads the image from the URL. Once the image downloads it will replace the placeholder image with the downloaded image.
  •     setImageForURL(url: NSString): Asynchronously downloads the image from the URL. Once the image is downloaded, it sets the image of the UIImageView to the downloaded image.
  •     setImageForRSTransaction(transaction:RSTransaction, placeHolder: UIImage): Sets the image in the UIImageView to the placeHolder image and then asynchronously downloads the image from the RSTransaction. Once the image downloads it will replace the placeholder image with the downloaded image.
  •     setImageForRSTransaction(transaction:RSTransaction): Asynchronously downloads the image from the RSTransaction. Once the image downloads it sets the image of the UIImageView to the downloaded image.
UIButton  
  •     setButtonImageForURL(url: NSString, placeHolder: UIImage, state: UIControlState): Sets the background image of the UIButton to the placeholder image and then asynchronously downloads the image from the URL. Once the image downloads it will replace the placeHolder image with the downloaded image.
  •     setButtonImageForURL(url: NSString, state: UIControlState): Asynchronously downloads the image from the URL. Once the download is complete, it will set the background image of the UIButton to the downloaded image.
  •     setButtonImageForRSTransaction(transaction:RSTransaction, placeHolder: UIImage, state: UIControlState): Sets the background image of the UIButton to the placeHolder image and then asynchronously downloads the image from the URL. Once the image downloads it will replace the placeHolder image with the downloaded image.
  •     setButtonImageForRSTransaction(transaction:RSTransaction, state: UIControlState): Asynchronously downloads the image from the URL. Once the download is complete, it will set the background image of the UIButton to the downloaded image.

Sample Code
This section contains sample code that show how to use RSNetworking

RSURLRequest
dataFromURL
let client = RSURLRequest()

if let testURL = NSURL(string:"https://itunes.apple.com/search?term=jimmy+buffett&media=music") {

   client.dataFromURL(testURL, completionHandler: {(response : NSURLResponse!, responseData: NSData!, error: NSError!) -> Void in
      if let error = error {
          print("Error : \(error)")
      } else {
          let string = NSString(data: responseData, encoding: NSUTF8StringEncoding)
          print("Response Data: \(string)")
      }
   })
}

dictionaryFromJsonURL
let client = RSURLRequest()

if let testURL = NSURL(string:"https://itunes.apple.com/search?term=jimmy+buffett&media=music") {

  client.dictionaryFromJsonURL(testURL, completionHandler: {(response : NSURLResponse!, responseDictionary: NSDictionary!, error: NSError!) -> Void in
      if let error = error {
          print("Error : \(error)")
      } else {
          print("Response Dictionary: \(responseDictionary)")
      }
   })
}

stringFromURL
let client = RSURLRequest()

if let testURL = NSURL(string:"https://itunes.apple.com/search?term=jimmy+buffett&media=music") {

  client.stringFromURL(testURL, completionHandler: {(response : NSURLResponse!, responseString: NSString!, error: NSError!) -> Void in
      if let error = error {
          print("Error : \(error)")
      } else {
          print("Response Data: \(responseString)")
      }
   })
}

imageFromURL
let client = RSURLRequest()

if let imageURL = NSURL(string:"http://a1.mzstatic.com/us/r30/Music/y2003/m12/d17/h16/s05.whogqrwc.100x100-75.jpg") {

  client.imageFromURL(imageURL, completionHandler: {(response : NSURLResponse!, image: UIImage!, error: NSError!) -> Void in
      if let error = error {
          print("Error : \(error)")
      } else {
          self.imageView?.image = image;
      }
   })
}

RSUtilities
RSUtilities.isHostnameReachable
  if (RSUtilities.isNetworkAvailable("www.apple.com")) {
     print("reachable")
 } else {
     print("Not Reachable")
 }

UIImageView: setImageForURL
let imageURL = "http://a1.mzstatic.com/us/r30/Music/y2003/m12/d17/h16/s05.whogqrwc.100x100-75.jpg"
 
imageView.setImageForURL(imageURL, placeHolder: UIImage(named: "loading"))   

  or

let imageURL = "http://a1.mzstatic.com/us/r30/Music/y2003/m12/d17/h16/s05.whogqrwc.100x100-75.jpg"

self.imageView?.setImageForURL(imageURL)

UIButton: setImageForURL
let imageURL = "http://a1.mzstatic.com/us/r30/Music/y2003/m12/d17/h16/s05.whogqrwc.100x100-75.jpg"

button.setButtonImageForURL(url, placeHolder: UIImage(named: "loading"), state:.Normal)

  or

let imageURL = "http://a1.mzstatic.com/us/r30/Music/y2003/m12/d17/h16/s05.whogqrwc.100x100-75.jpg"

button.setButtonImageForURL(url, state:.Normal)

RSTransactionRequest
RSTransactionRequest is designed to be used when you need to create mulitple requests to the same service. It allows you to set up the request once and then just change the parameters for each request

dictionaryFromRSTransaction
let rsRequest = RSTransactionRequest()

//Create the initial request
let rsTransGet = RSTransaction(transactionType: RSTransactionType.GET, baseURL: "https://itunes.apple.com", path: "search", parameters: ["term":"jimmy+buffett","media":"music"])

rsRequest.dictionaryFromRSTransaction(rsTransGet, completionHandler: {(response : NSURLResponse!, responseDictionary: NSDictionary!, error: NSError!) -> Void in
    if let error = error {
        print("Error : \(error)")
    } else {
        print(responseDictionary)
    }
})


Now that you have the RSTransaction, you can simply change the parameters and make another request, if needed, like this:
 
let rsRequest = RSTransactionRequest()

//Create the initial request
rsTransGet.parameters = ["term":"Jimmy", "media":"music"]

rsRequest.dictionaryFromRSTransaction(rsTransGet, completionHandler: {(response : NSURLResponse!, responseDictionary: NSDictionary!, error: NSError!) -> Void in
    if let error = error {
        print("Error : \(error)")
    } else {
        print(responseDictionary)
    }
})

stringFromRSTransaction
 
//Change parameters from the previously example so we can make a second request
rsTransGet.parameters = ["term":"Jimmy", "media":"music"]
rsRequest.stringFromRSTransaction(rsTransGet, completionHandler: {(response : NSURLResponse!, responseString: NSString!, error: NSError!) -> Void in
    if let error = error {
        print("Error : \(error)")
    } else {
        print(responseString)
    }
})


If there is a feature that you would like to see in RSNetworking 2, please leave a comment to this blog post or in the github site here:  https://github.com/hoffmanjon/RSNetworking2.  If you would like to contribute to RSNetworking 2, please feel free to do so.