iOS: Writing test cases & Restructuring to testable code (Part II)

Tips for tests dealing with foundation notification objects

iOS: Writing test cases & Restructuring to testable code (Part II)

Tips for tests dealing with foundation notification objects

I’d like to talk about is some best practices for testing notifications. And to clarify, by notification here, I’m talking about foundation-level notifications known as NSNotification and Objective-C/Swift.

Here, I have the PointsOfInterest TableViewController from the app mentioned in WWDC 2018. It shows a list of nearby places of interest in a table view, and whenever the app’s location authorization changes, it may need to reload its data.

So, it observes a notification called authChanged from our app’s CurrentLocationProvider class.When it observes this notification, it reloads its data if necessary, and, then, for the purpose of this example, it sets a flag.

So, we’d like to isolate this code better to test this. There’s a technique we can use to better isolate these tests. To use it, we first have to recognize that NotificationCenter can have multiple instances. As you may note, it has a default instance as a class property, but it supports creating additional instances whenever necessary, and this is going to be key to isolating our tests.

So, to apply this technique, we first have to create a new NotificationCenter, pass it to our subject and use it instead of the default instance. This is often referred to as dependency injection. So, let’s take a look at using this in our view controller.

Here, I have the original code that uses the default NotificationCenter, and I’ll modify it to use a separate instance. I’ve added a new NotificationCenter property and a parameter in the initializer that sets it.

And, instead of adding an observer to the default center, it uses this new property.

I’ll also add a default parameter value of .default to the initializer, and this avoids breaking any existing code in my app, since existing clients won’t need to pass the new parameter, only our unit tests will.

XCTNSNotificationExpectation

An expectation that is fulfilled when an expected NSNotification is received.

How to validate that a subject posts a Notification?

  • Use a separate NotificationCenter again
  • Use XCTNSNotificationExpectation

Now let’s go back and write our tests. Here’s the original test code to use a separate NotificationCenter. So, this shows how we can test that our subject observes a notification, but how do we test that our subject posts a notification? We’ll use the same separate NotificationCenter trick again, but I’ll also show how to make use of built-in expectation APIs to add a notification observer.

And here’s a unit test I wrote for this class. It verifies that it posts a notification whenever the NotifyAuthChanged method is called, and we can see in the middle section here that this test uses the addObserver method to create a block-based observer, and then it removes that observer inside of the block.

When our tests are expecting to receive a notification to a specific center, we can pass the NotificationCenter parameter to the initializer of the expectation. I’d also like to point out that the timeout of this expectation is 0, and that’s because we actually expect it to already have been fulfilled by the time we wait on it. That’s because the notification should have already been posted by the time the NotifyAuthChanged method returns.

So, using this pair of techniques for testing notifications we can ensure that our tests remained fully isolated, and we’ve made the change without needing to modify an existing code in our app, since we specified that default parameter value.

Coming Next: Part 3 (Advantage of protocols when making mock objects in your tests)

References:

  1. https://developer.apple.com/videos/play/wwdc2018/417/?time=44