Unit testing has a stigma of slowing you down while you’re trying to rock some code. The truth is unit testing doesn’t have to be so dry. Once you start flipping the script, unit testing can be an opportunity for refactoring, refreshing, and finding bugs. Besides, who wouldn’t want to see a bunch of green checkmarks next to their code?:
A beauty isn’t it? This is telling me that my code works, in the midsts of all the changing versions of platforms, frameworks, libraries, SDK’s, etc… it’s a miracle really. These unit test passes brings a new sense of confidence to the code and overall applications that depend on it.
Getting Started
So you’re convinced that unit testing can actually be fun. Check out how quick it is to set up.
- Add unit testing to any new or existing project. When creating a new project, ensure the “Include Unit Tests” is checked. For an existing project, highlight your project file in Xcode and you will see the project and targets pane. Click the “Add a target” button with the plus icon, or find it in the menu under File > New > Target.
- Choose “iOS Unit Testing Bundle” from the iOS > Test templates. You can select a test target from another platform – just ensure it matches the project platform you are testing against (where your code is).
- On the next screen, you will name your test project. The important thing here is to select the right target to be tested (again, where your code to test resides).
- Xcode will create a sample test class for you to try:
- Click on the diamond icons in the code gutter next to the whole class or an individual function:
Booom! Take a look at those soothing, green icons – this indicates all you’re unit tests were successful. Congratulations, you’ve just run unit testing in Swift and Xcode!
Breaking It Down
There is some convention to get out the way that seem simple and straight-forward. Unit test classes inherit from XCTestCase. This base class has overridable setUp and tearDown functions which will run before and after EACH test (not each instance of XCTestCase). setUp is where you create instances, load data, set up cache, etc; then in tearDown you destroy instances if needed, clear data, clear cache, etc.
Functions that are prefixed with the word “test” is considered a unit test, then gets those nifty diamond icons to run the test. Feel free to create properties or functions in your test class to facilitate your unit testing – just don’t prefix them with “test” unless it is a unit test.
Show Me the Code!!
Real code and logic will paint the whole picture so let’s jump right in. Say we have this super uber utility helper that we would like to test:
public struct WebHelper { /** Add, update, or remove a query string parameter from the URL - parameter url: the URL - parameter key: the key of the query string parameter - parameter value: the value to replace the query string parameter, nil will remove item - returns: the URL with the mutated query string */ public func addOrUpdateQueryStringParameter(url: String, key: String, value: String?) -> String { if let components = NSURLComponents(string: url), var queryItems: [NSURLQueryItem] = (components.queryItems ?? []) { for (index, item) in queryItems.enumerate() { // Match query string key and update if item.name.lowercaseString == key.lowercaseString { if let v = value { queryItems[index] = NSURLQueryItem(name: key, value: v) } else { queryItems.removeAtIndex(index) } components.queryItems = queryItems.count > 0 ? queryItems : nil return components.string! } } // Key doesn't exist if reaches here if let v = value { // Add key to URL query string queryItems.append(NSURLQueryItem(name: key, value: v)) components.queryItems = queryItems return components.string! } } return url } /** Removes a query string parameter from the URL - parameter url: the URL - parameter key: the key of the query string parameter - returns: the URL with the mutated query string */ public func removeQueryStringParameter(url: String, key: String) -> String { return addOrUpdateQueryStringParameter(url, key: key, value: nil) } }
The code is not the point, but it is to illustrate real world logic for context. This particular function adds and removes query string parameters from a given URL string.
Now let’s unit test this!!!
The Real Unit Test
In your unit test target, add a new Swift file. For convention, I like to keep the unit test class in the same folder structure as what I’m testing and append the word “Tests” at the end of the class name. Also, be sure the target membership is marked for your unit test target.
Here’s what the unit test code can look like for my corresponding WebHelper class:
import Foundation import XCTest class WebTimeHelperTests: XCTestCase { var webHelper: WebHelper! override func setUp() { super.setUp() webHelper = WebHelper() } func testAddOrUpdateQueryStringParameter() { let value = "https://example.com?abc=123&lmn=tuv&xyz=987" let newValue = webHelper.addOrUpdateQueryStringParameter(value, key: "aBc", value: "555") let expectedValue = "https://example.com?aBc=555&lmn=tuv&xyz=987" XCTAssertEqual(newValue, expectedValue, "String should be \(expectedValue)") } func testRemoveQueryStringParameter() { let value = "https://example.com?abc=123&lmn=tuv&xyz=987" let newValue = webHelper.removeQueryStringParameter(value, key: "xyz") let expectedValue = "https://example.com?abc=123&lmn=tuv" XCTAssertEqual(newValue, expectedValue, "String should be \(expectedValue)") } func testAddOrUpdateQueryStringParameterForAdd() { let value = "https://example.com?abc=123&lmn=tuv&xyz=987" let newValue = webHelper.addOrUpdateQueryStringParameter(value, key: "def", value: "456") let expectedValue = "https://example.com?abc=123&lmn=tuv&xyz=987&def=456" XCTAssertEqual(newValue, expectedValue, "String should be \(expectedValue)") } }
In each test function, I’m calling my web helper API, such as addOrUpdateQueryStringParameter, with sample data to see if it is indeed adding or removing query string parameters from the URL value. I’m doing this by storing the result in a variable. Then I am comparing them to hard-coded results that I know are true – using XCTAssert. That’s all there is to it!
It all comes down to XCTAssert and it’s variations, which takes on a boolean as a parameter value: XCTAssert(expression,…). If the parameter is true, the unit test passes; if the parameter is false, then it fails. In my case, I’m using XCTAssertEqual, which compares two values and passes if they match.
Tip and Tricks
It’s good to note that you can see a hierarchical view of all your tests and run them at once from the “Test Navigator” with the button with the diamond with the line in the middle:
Right-click and enable if the unit tests are grayed out. Then you can run the unit tests globally from here instead of in the code editor.
Also, if you get any build errors with the unit tests, they won’t show up as normal with the other build errors. You must look at them from the “Report Navigator” pane.
Another thing is in the “Breakpoint Navigator” pane. Click the plus sign in the bottom left corner and select “Add Test Failure Breakpoint”. You will get a test failure breakpoint that will always stop the debugger if any of your unit tests fail. Very convenient so you can see the live values of the variables during the point of failure and step through it from there.
Feel free to add other breakpoints in your project code and they will be hit as normal too!
Conclusion
There’s really no excuse for skipping unit testing for any solution. It is easy to set up and the peace of mind goes a long way. More importantly, your code is now bug-free, well almost 😉
HAPPY CODING!!
To download the sample, check out the Github project and try for yourself.
[…] the “Target Memberships” you’d like to support for that particular code file. And don’t forget to unit test… […]