Adoption Curve Dot Net

Testing Asynchronous Code in Swift

| Comments

Asynchronous code is both hard to test; and important to test.

It’s important, because it often handles some of the most critical and yet unreliable parts of an app - in other words, networking.

It’s hard because it’s, well, asynchronous. One of the two jokes in software engineering goes something like “Some people, when faced with a problem, think ‘I know, I’ll use threading’. Now problems have two they.”

The standard rhythm of a unit test goes like this:

  • set up the preconditions for the test, to get the system into a known state
  • make some assertions about what result you’re looking for
  • fire the functions under test
  • verify that the assertions were met

The problem with asynchronous code is that the third and final steps don’t always come in that order, particularly if you’re attemping to test a long-running operation. It can still be churning away as your verification takes place - and your tests are unlikely to pass in that kind of scenario.

Fortunately, there are ways around this. It’s not especially pretty, but it does work.

waitForExpectations

The key to testing async code in Swift is using the waitForExpectationWithTimeout feature of the XCTest framework. An expectation in this context is just a flag that tells the test code “I’m done, you can check your assertions now”.

The expectation will hang around and wait for the fulfillment message, which gives the async code a chance to complete before the assertions are checked.

There are three stages involved in setting this up:

  • Creating an expectation
  • Calling the async function, and then flagging that the expection has been fulfilled when your code has comepleted
  • Wrapping the assertions inside a waitForExpectationWithTimeout block so that they will wait for the fulfillment to occor.

Creating the expectation

This is quite simple. At the top of your test function, create an expectation with

1
let asyncExpectation = expectationWithDescription("longRunningFunction")

This needs to be done before you call the async code.

Fulfilling the expectation

With the expectation created, you need to signal that it’s fulfilled after your asynchronous code completes. Here’s an example of a function that fires off a network request, and takes a completion handler as a parameter to process the data that’s returned:

1
func networkManager.getDataFromApiForUser(user: User, completionHandler: (userData: NSData?, error: NSError?) -> () )

In normal situations, you’d use this function with something like this:

1
2
3
4
5
6
7
networkManager.getDataFromApiForUser(testUser, completionHandler: { (userData, error) -> () in
  
  ... handle the error if one occurs
  ...
  ... otherwise process the userData object
  
)}

Testing the function in a unit test is very similar:

1
2
3
4
5
6
7
8
9
networkManager.getDataFromApiForUser(testUser, completionHandler: { (userData, error) -> () in

  ... handle the error if one occurs
  ...
  ... otherwise process the userData object

  expectation.fulfill()
  
)}

The key difference is that last line - expectation.fulfill(). What we’re doing here is to signal to the asyncExpectation that everything is completed, and it’s now safe to check the assertions.

Checking the assertions

Assertions in this context are just the usual XCTest assertions that you’ll already be familar with. What’s different is that we wrap them in a waitForExpectationsWithTimeout block:

1
2
3
4
5
6
self.waitForExpectationsWithTimeout(5) { error in
  
  XCTestAssertNil(error, "Something went horribly wrong")
  XCTestAssertEqual(testUser.orders.count, 10)
  
}

This function will do two things - firstly, it will wait for a maximum of five seconds before giving up and failing. This prevents you from locking up the tests in an infinite loop if something goes wrong with the code under test. How long to wait is dependent on the kind of processing that’s taking place - but ideally shouldn’t be so long that it locks up the test suite unncessarily.

Secondly, it will fire the XCTest expectations as soon as the expectation.fulfill() function is called. By triggering that at the end of the chunk of async code, you’re preventing the assertions racing away and being checked before the function under test has had a chance to complete.

Putting it all together

Putting that all together in a example test looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func testUserOrdersAreRetrievedFromApiAndProcessed() {

  let asyncExpectation = expectationWithDescription("longRunningFunction")
  
  let testUser = User.initWithName("Foo" andAccountNumber: 1234)
  
  networkManager.getDataFromApiForUser(testUser, completionHandler: { (userData, error) -> () in
      
      ... handle the error if one occurs
      ...
      ... otherwise process the userData object
      
      expectation.fulfill()
      
  )}
  
  self.waitForExpectationsWithTimeout(5) { error in
      
      XCTestAssertNil(error, "Something went horribly wrong")
      XCTestAssertEqual(testUser.orders.count, 10)
      
  }

}

It’s not the cleanest looking test code you’ll ever write, especially if you’re not a fan of the Junit-style syntax of the XCTest framework - but it works. And given how important asynchronous code like network requests or long-running processing tends to be to an app, it’s worth sacrificing a little style for the reassurance that high test coverage will provide.

Mocks and Expectations With Swift

| Comments

A new language, a new way of doing things - including testing. One of the harder parts of getting to grips with Swift has been figuring out how testing works, and in particular how to do more extensive stuff like mocking and stubbing.

By way of background, mocking is the art of creating “stunt doubles” for your classes that are involved in tests. You’d do this for a couple of reasons - either to manipulate your stunt doubles into a known state for testing purpose; or because it’s expensive or difficult to use the real thing.

An example of this might be testing what happens if a network service doesn’t return the result that you were expecting. If you’re testing the use of a live service, it might be tricky to create a deliberate failure just for testing purposes. Far easier to create a stunt double and use this to return the value that your tests require.

The other situation where you may want a stand-in object is if you want to verify that a method has been called. Say for example you’ve got some kind of callback, and you want to test whether this gets fired. By setting an expectation on the callback method, you can verify that it actually was called - which is especially useful if you’re testing asynchronous operations.

It was perfectly possible to do all of this in Objective-C, but it required the use of external libraries such as OCMock or Kiwi. Swift, on the other hand, is much easier. The structure of the language makes it very straight-forward to implement basic mocking and stubbing operations, without the need for external pods or libraries.

Here’s an example. Say we have a class called SooperClass, and this has a function called someExpensiveFunctionThatReturnsAStringFromTheWeb. We don’t want to really fire this as part of our tests, because (for the sake of this example) we get charged every time we make an API call. Instead, we need to mock it.

The first step is to create a mock of SooperClass inside our test class. At the top of the test class, create a FakeSooperClass which inherits from SooperClass like this:

1
2
3
4
5
6
7
8
9
class MockrTests: XCTestCase {

    class FakeSooperClass: SooperClass {

    }

    ... rest of the test class here ...

}

Next, override the method that you want to test:

1
2
3
4
5
6
7
class FakeSooperClass: SooperClass {

    override func someExpensiveFunctionThatReturnsAStringFromTheWeb() -> String {
  
    }

}

And use this overridden method to return whatever data your test needs:

1
2
3
override func someExpensiveFunctionThatReturnsAStringFromTheWeb() -> String {
    return "Finally! Some content!"
}

Now you can use this in your test:

1
2
3
4
5
6
7
8
func testThatTheExpensiveFunctionReturnsSomethingUseful() {

    let fakeSooperClass = FakeSooperClass()
    let stringFromTheWeb = fakeSooperClass.someExpensiveFunctionThatReturnsAStringFromTheWeb()

  ... use the value in the rest of the test ...
  
}

Here, we’ve created a mock instance of SooperClass, and used this to return data in the format that we need for the rest of the test. But in doing so, we haven’t had to call out to the public API that SooperClass would normally talk to.

We can take the fake class one step further, and test that the method actually gets called. We’ll set an expectation that the function will be called once during the code that we’re testing - if it isn’t, something has gone wrong somewhere.

To do this, we need to extend our fake class slightly, to include a flag:

1
2
3
4
5
6
7
8
class FakeSooperClass: SooperClass {

    var didCallExpensiveFunctionFlag = false

    override func someExpensiveFunctionThatReturnsAStringFromTheWeb() -> String {
        return "Finally! Some content!"
    }
}

This flag is initially false (because the function hasn’t been called yet). When the function is called, we’ll flip the flag inside the fake class. By getting the test to check the state of the flag, it will be possible to see if the function was called as we expected.

This needs a tweak to our fake someExpensiveMethodThatReturnsAStringFromTheWeb() function:

1
2
3
4
5
6
7
8
9
class FakeSooperClass: SooperClass {

    var didCallExpensiveFunctionFlat = false

    override func someExpensiveMethodThatReturnsAStringFromTheWeb() -> String {
        didCallExpensiveFunctionFlag = true
        return "Finally! Some content!"
    }
}

Now we can test all this in our test case:

1
2
3
4
5
6
7
8
9
10
11
12
func testThatTheExpensiveFunctionGetsCalled() {

    let fakeSooperClass = FakeSooperClass()

    // Code that eventually ends up with someExpensiveFunctionThatReturnsAStringFromTheWeb() being called,
    // like this:
    // let result = fakeSooperClass.someExpensiveFunctionThatReturnsAStringFromTheWeb()

    // Set an assertion to check that the flag
    XCTAssert(fakeSooperClass.didCallExpensiveFunction == true, "expected function to be called, but wasn't")

}

If the function WAS called, the flag will be flipped, and the assertion will pass. If for some reason the function didn’t get called, then the flag will be false and will cause the assertion to fail.

You could also extend this to keep track of the number of times the function gets called, by incrementing a counter. It uses exactly the same approach of overriding a function in a fake class, and checking the results with an assertion.

So, not difficult to do. Not only that, but rolling your own mocks and expectations in Swift also has the benefit that you’re not dependent on external 3rd party libraries with all the attendent complexities that these can sometimes bring along.

Testing NSUserDefaults

| Comments

NSUserDefaults is where you can store persistent app settings, as an alternative to storing them in some kind of in-app database. You shouldn’t try to store actual application data in NSUserDefaults, but they are useful for capturing “small” settings like environment URLs and so on.

If you’re using NSUserDefaults, you should be testing NSUserDefaults. Here’s how.

One of the most common testing scenarios is that you want to store some value in the defaults store so that it can be read out later. This is the process for testing that your code actually does so. Assume that we’ve got a class called myClass which has an IBAction method called didTapUpdateSettingsButton which will write out a specific value.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
it(@"should write the configured environment to the user defaults"), ^{

  NSString *apiUrl = @"http://foo.com/bar";
  NSString *theKey = @"defaultApiUrl";

  id mockStandardDefaults = [NSUserDefaults nullMock];
  
  [NSUserDefaults stub:@selector(standardUserDefaults) andReturn:mockStandardDefaults];
  
  [[mockStandardDefaults should] receive:@selector(setObject:forKey:) withArguments:theObject, theKey];
  
  [[mockStandardDefaults should] receive:@selector(sychronize)];
  
  [myClass didTapUpdateSettingsButton:nil];

}

Walking through this, we first create variables for the key and object that we are expecting the didTapUpdateSettings method to attempt to write to the defaults store.

Next, we create a mock object which will stand in for the instance of the NSUserDefaults class that would normally be returned by the [NSUserDefaults standardUserDefaults] method. We’re going to use this mock to catch and test the messages that our code under test sends. It’s created as a null mock here so that it won’t complain if it gets messages other than the ones that we send it.

Then we need to stub the standardUserDefaults method of the real, live NSUserDefaults class, and return our mock instance instead of the real thing.

Once we’ve stubbed out NSUserDefaults, we can now set some expectations about what our mock standardUserDefaults will receive. There are two things we’re interested in - firstly, that it receives the setObject:forKey message with the values that we expect. If that message isn’t received, or the values differ from what we expect, then there’s something wrong with the code that we’re writing.

Secondly, we’re also setting an expectation that the mock receives the synchronize message. This saves the values that we’ve updated - not sending a synchronize message to NSUserDefaults is a common mistake, and hard to diagnost - with a specific test, we’ll catch any situations where we forget that final step.

Finally, we invoke the code under test by “tapping” the button (or in this case, calling the IBAction method that the button is wired up to).

If you’re taking a test-first approach to writing the code, obviously this will initially fail. But by fixing each failing assertion in turn, you’ll end up with a method that both writes and saves the correct values.

Where Has the iOS8 Simulator Gone?!?

| Comments

With Xcode 6 and the new iOS8 Simulators, Apple thought it would be a good idea to move the location of the Simulator files. That’s not usually a problem, because there’s only a few specific circumstances when you need to access the files underlying the Simulator’s functions.

If you do need to access those files, the chances are that you can’t find them anymore.

Instead of looking in

~/Library/Application Support/iPhone Simulator/

you’ll find them instead in

~/Library/Developer/CoreSimulator/Devices/*

Which at least is easier to embed into scripts thanks to the lack of spaces in the path.

Working With Size Classes in Interface Builder

| Comments

Android used to be notorious amongst iOS developers for its practically infinite permutations of interface size. Viewed from the iOS world, this used to look like a problem, because iOS didn’t really provide much in the way of support for building interfaces of different sizes.

If you were building a universal app that supported both iPhone and iPad, there was a tendency to end up with a lot of if deviceType == kIpad-style code.

AutoLayout was the first part of fixing that problem, and the job’s been completed with iOS 8 and size classes. This is probably the least-sexy feature introduced in 8, but it’s definitely one of the more important.

Some quick background on size classes

There are currently two size classes - horizontal and vertical, and each one comes in two sizes - regular and compact. The current orientation of the device can be described as a combination of the sizes:

  • Horizontal regular, vertical regular: iPad in either orientation
  • Horizontal compact, vertical regular: iPhone portrait
  • Horizontal regular, vertical compact: no current device
  • Horizontal compact, vertical compact: iPhone landscape

Storyboards and nib files now support these size classes - so you can think of them as having up to four different layouts contained within the same file. At the bottom of the Interface Builder window, there’s now a control that allows you to switch between each combination:

Every control or AutoLayout constraint can exist in one, several or all of the size classes. This means that you can build interfaces that change depending on device type and/or orientation without any code. Controls can appear or disappear, change size, or change arrangements - all based on the layout that you create in Interface Builder.