Unit Testing Tutorial: Mocking Objects

In this tutorial you’ll learn how to write your own mocks, fakes and stubs to test a simple app that helps you remember your friends birthdays. By .

Leave a rating/review
Save for later
Share

Screen Shot 2015-05-07 at 3.10.07 PM

Who needs unit tests? Not you — your code is perfect. Umm…so you’re just reading this tutorial for your “friend” who needs to learn more about writing unit tests in Swift, right? Right. :]

Unit tests are a great way to write better code; tests help you find most of the bugs early on in the process, but more importantly, writing code in a test-based development mindset helps you write modular code that’s easy to maintain. As a rule of thumb: if your code isn’t easy to test, it’s not going to be easy to maintain or debug.

Unit tests deal with isolated “micro features”. Often you need to mock classes — that is, provide fake yet functional implementations — to isolate a specific micro feature so it can be tested. In Objective-C there are several third-party frameworks that help with mocking and stubbing. But those rely on introspection, which isn’t yet available on Swift. Someday, hopefully! :]

In this tutorial you’ll learn how to write your own mocks, fakes and stubs to test a simple app that helps you remember your friends birthdays.

Getting Started

Download the starter project here; this is a basic contacts app that can be hooked up to a web backend. You won’t work on the core app functionality; rather, you’ll write some tests for it to make sure it behaves as expected.

Build and run your app to see how it works. Tap the plus sign and add good ol’ John Appleseed to your list:

iOS Simulator Screen Shot 31.03.2015 21.55.29

The sample app uses Core Data to store your contacts.

ragecomic_coredata

Don’t panic! :] You don’t need any experience with Core Data for this tutorial; there’s no rocket science involved.

Note: If you do want to become a Core Data master, you can get started by reading this Core Data: Getting Started tutorial.

Advantages and Disadvantages of Unit Tests

When it comes to testing, there’s good news, and bad news. The bad news is that there can be disadvantages to unit tests, like the following:

  • More code: In projects with high test coverage it’s possible to have more test code than functional code.
  • More to maintain: When there is more code, there is more to maintain.
  • No silver bullet: Unit tests don’t (and can’t) ensure that your code is free of bugs.
  • Takes longer: Writing tests takes time — time you could spend learning new exciting stuff on raywenderlich.com!

Although there is no silver bullet, there is a silver lining — testing has the following advantages:

  • Confidence: You can demonstrate that your code works.
  • Quick feedback: You can use unit tests to quickly validate code that is buried many layers deep in your app navigation — things that are cumbersome to test manually.
  • Modularity: Unit tests help keep you focused on writing more modular code.
  • Focus: Writing tests for micro features keep you focused on the small details.
  • Regression: Be sure that the bugs you fixed stay fixed — and aren’t broken by subsequent fixes.
  • Refactoring: Until Xcode gets smart enough to refactor your code on its own, you’ll need unit tests to validate your refactoring.
  • Documentation: Unit tests describe what you think the code should do; they serve as another way to document your code.

The Basic App Structure

A lot of the code in the sample app is based on the Master-Detail Application template with Core Data enabled. But there are some significant improvements over the template code. Open the sample project in Xcode and have a look at the project navigator:

Screen Shot 2015-03-28 at 16.59.36

Take note of the following details:

  • There is a Person.swift and a PersonInfo.swift file. The Person class is an NSManagedObject that contains some basic information about each person. The PersonInfo struct contains the same information but can be instanced from the address book.
  • The folder PeopleList has three files: A view controller, a data provider and a data provider protocol.

The file collection in PeopleList is an attempt to avoid massive view controllers. It’s good practice to avoid massive view controllers by moving some responsibilities into other classes that communicate with the view controllers via a simple protocol. You can learn more about massive view controllers and how to avoid them by reading this interesting albeit older article.

In this case, the protocol is defined in PeopleListDataProviderProtocol.swift; open it and have a look. A class conforming to this protocol must have the properties managedObjectContext and tableView and must define the methods addPerson(_:) and fetch(). In addition, it must conform to the UITableViewDataSource protocol.

The view controller PeopleListViewController has a property dataProvider, which conforms to PeopleListDataProviderProtocol. This property is set to an instance of PeopleListDataProvider in AppDelegate.swift.

You add people to the list using ABPeoplePickerNavigationController. This class lets you, the developer, access the user’s contacts without requiring explicit permission.

PeopleListDataProvider is responsible for filling the table view and for talking to the Core Data persistent store.

Note: Several classes and methods in the starter project are declared as public; this is so the test target can access those classes and methods. The test target is outside of the app module. If you don’t add any access modifier the classes and methods are defined as internal. This means they are only accessible within the same module. To access them from outside the module (for example from the test target) you need to add the public access modifier.

That’s enough overview — time to start writing some tests!

Writing Mocks

Mocks let you check if a method call is performed or if a property is set when something happens in your app. For example, in viewDidLoad() of PeopleListViewController, the table view is set to the tableView property of the dataProvider.

You’ll write a test to check that this actually happens.

Preparing Your App for Testing

First, you need to prepare the project to make testing possible.

Select the project in the project navigator, then select Build Settings in the Birthdays target. Search for Defines Module, and change the setting to Yes as shown below:

Screen Shot 2015-03-28 at 17.40.12

Next, select the BirthdaysTests folder and go to File\New\File…. Select a iOS\Source\Test Case Class template, click Next, name it PeopleListViewControllerTests, ensure you’re creating a Swift file, click Next, then finally click Create.

If Xcode prompts you to create a bridging header, select No. This is a bug in Xcode that occurs when there is no file in the target and you add a Swift file.

Open the newly created PeopleListViewControllerTests.swift. Import the module you just enabled by adding the import Birthdays statement right after the other import statements as shown below:

import UIKit
import XCTest
import Birthdays

Remove the following two template test methods:

func testExample() {
  // This is an example of a functional test case.
  XCTAssert(true, "Pass")
}

func testPerformanceExample() {
  // This is an example of a performance test case.
  self.measureBlock() {
    // Put the code you want to measure the time of here.
  }
}

You now need an instance of PeopleListViewController so you can use it in your tests.

Add the following line to the beginning of PeopleListViewControllerTests:

var viewController: PeopleListViewController!

Replace the setUp() method with the following code:

override func setUp() {
  super.setUp()
  
  viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("PeopleListViewController") as! PeopleListViewController
}

This uses the main storyboard to create an instance of PeopleListViewController and assigns it to viewController.

Select Product\Test; Xcode builds the project and runs any existing tests. Although you don’t have any tests yet, this is a good way to ensure everything is set up correctly. After a few seconds, Xcode should report that all tests succeeded.

You’re now ready to create your first mock.

Writing Your First Mock

Since you’re going to be working with Core Data, add the following import to the top of PeopleListViewControllerTests.swift, right below import Birthdays:

import CoreData

Next, add the following code within the class definition of PeopleListViewControllerTests:

class MockDataProvider: NSObject, PeopleListDataProviderProtocol {
  
  var managedObjectContext: NSManagedObjectContext?
  weak var tableView: UITableView!
  func addPerson(personInfo: PersonInfo) { }
  func fetch() { }
  func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 1 }
  func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    return UITableViewCell()
  }
}

This looks like a quite complicated mock class. However, this is just the bare minimum required, as you’re going to set an instance of this mock class to the dataProvider property of PeopleListViewController. Your mock class also has to conform to the PeopleListDataProviderProtocol as well as the UITableViewDataSource protocol.

Select Product\Test; your project will compile again and your zero tests will run with zero failures. Sorry — that doesn’t count at a 100% pass rate. :] But now you have everything set up for the first unit test using a mock.

It’s good practice to separate the unit tests in three parts called given, when and then. ‘Given’, sets up the environment; ‘when’ executes the code you want to test; and ‘then’ checks for the expected result.

Your test will check that the tableView property of the data provider is set after viewDidLoad() has been executed.

Add the following test to PeopleListViewControllerTests:

func testDataProviderHasTableViewPropertySetAfterLoading() {
  // given
  // 1
  let mockDataProvider = MockDataProvider()
  
  viewController.dataProvider = mockDataProvider
  
  // when
  // 2
  XCTAssertNil(mockDataProvider.tableView, "Before loading the table view should be nil")
  
  // 3
  let _ = viewController.view
  
  // then    
  // 4
  XCTAssertTrue(mockDataProvider.tableView != nil, "The table view should be set")
  XCTAssert(mockDataProvider.tableView === viewController.tableView, 
    "The table view should be set to the table view of the data source")
}

Here is what the above test is doing:

  1. Creates an instance of MockDataProvider and sets it to the dataProvider property of the view controller.
  2. Asserts that the tableView property is nil before the test.
  3. Accesses the view to trigger viewDidLoad().
  4. Asserts that the test class’ tableView property is not nil and that it is set to the tableView of the view controller.

Select Product\Test again; once the tests have finished, open the test navigator (Cmd+5 is a handy shortcut). You should see something like the following:

Screen Shot 2015-03-29 at 10.54.20

Your first test with a mock passed with flying colors! :]

Testing addPerson(_:)

The next test is to ensure selecting a contact from the list calls addPerson(_:) of the data provider.

Add the following property to the MockDataProvider class:

var addPersonGotCalled = false

Next, change addPerson(_:) to the following:

func addPerson(personInfo: PersonInfo) { addPersonGotCalled = true }

Now when you call addPerson(_:), you’ll register this in an instance of MockDataProvider by setting addPersonGotCalled to true.

You’ll have to import the AddressBookUI framework before you can add a method to test this behavior.

Add the following import right below the other imports in PeopleListViewControllerTests.swift:

import AddressBookUI

Now add the following test method with the rest of the test cases:

func testCallsAddPersonOfThePeopleDataSourceAfterAddingAPersion() {
  // given
  let mockDataSource = MockDataProvider()
  
  // 1
  viewController.dataProvider = mockDataSource
  
  // when
  // 2
  let record: ABRecord = ABPersonCreate().takeRetainedValue()
  ABRecordSetValue(record, kABPersonFirstNameProperty, "TestFirstname", nil)
  ABRecordSetValue(record, kABPersonLastNameProperty, "TestLastname", nil)
  ABRecordSetValue(record, kABPersonBirthdayProperty, NSDate(), nil)
  
  // 3
  viewController.peoplePickerNavigationController(ABPeoplePickerNavigationController(), 
    didSelectPerson: record)
  
  // then
  // 4
  XCTAssert(mockDataSource.addPersonGotCalled, "addPerson should have been called")
}

So what’s going on here?

  1. First you set the data provider of the view controller to an instance of your mock data provider.
  2. Then you create a contact by using ABPersonCreate().
  3. Here you manually call the delegate method peoplePickerNavigationController(_:didSelectPerson:). Normally, calling delegate methods manually is a code smell, but it’s fine for testing purposes.
  4. Finally you assert that addPerson(_:) was called by checking that addPersonGotCalled of the data provider mock is true.

Select Product\Test to run the tests — they should all pass. Hey, this testing thing is pretty easy!

But wait! How do you know that the tests actually test what you think they’re testing?

ragecomic_wat

Testing Your Tests

A quick way to check that a test is actually validating something is to remove the entity that the test validates.

Open PeopleListViewController.swift and comment out the following line in peoplePickerNavigationController(_:didSelectPerson:):

dataProvider?.addPerson(person)

Run the tests again; the last test you wrote should now fail. Cool — you now know that your test is actually testing something. It’s good practice to test your tests; at the very least you should test your most complicated tests to be sure they work.

ragecomic_testsOfTests

Un-comment the line to get the code back to a working state; run the tests again to make sure everything is working.

Mocking Apple Framework Classes

You may have used singletons such as NSNotificationCenter.defaultCenter() and NSUserDefaults.standardUserDefaults() — but how would you test that a notification is actually sent or that a default is set? Apple doesn’t allow you to inspect the state of these classes.

You could add the test class as an observer for the expected notifications. But this might cause your tests to become slow and unreliable since they depend on the implementation of those classes. Or the notification could be sent from another part of your code, and you wouldn’t be testing an isolated behavior.

To get around these limitations, you can use mocks in place of these singletons.

Note: When you replace Apple’s classes with mocks, it’s very important to only test the interaction with that class, not the behavior of that class, as implementation details could change at any point.

Build and run your app; add John Appleseed and David Taylor to the list of people and toggle the sorting between ‘Last Name’ and ‘First Name’. You’ll see that the order of the contacts in the list depends on the sort order of the table view.

The code that’s responsible for sorting lives in changeSort() in PeopleListViewController.swift:

@IBAction func changeSorting(sender: UISegmentedControl) {
    userDefaults.setInteger(sender.selectedSegmentIndex, forKey: "sort")
    dataProvider?.fetch()
}

This adds the selected segment index for the key sort to the user defaults and calls fetch() on the data provider. fetch() should read this new sort order from the user defaults and update the contact list, as demonstrated in PeopleListDataProvider:

public func fetch() {
  let sortKey = NSUserDefaults.standardUserDefaults().integerForKey("sort") == 0 ? "lastName" : "firstName"

  let sortDescriptor = NSSortDescriptor(key: sortKey, ascending: true)
  let sortDescriptors = [sortDescriptor]

  fetchedResultsController.fetchRequest.sortDescriptors = sortDescriptors
  var error: NSError? = nil
  if !fetchedResultsController.performFetch(&error) {
    println("error: \(error)")
  }
  tableView.reloadData()
}

PeopleListDataProvider uses an NSFetchedResultsController to fetch data from the Core Data persistent store. To change the sorting of the list, fetch() creates an array with sort descriptors and sets it to the fetch request of the fetched results controller. Then it performs a fetch to update the list and call reloadData() on the table view.

You’ll now add a test to ensure the user’s preferred sort order is correctly set in NSUserDefaults.

Open PeopleListViewControllerTests.swift and add the following class definition right below the class definition of MockDataProvider:

class MockUserDefaults: NSUserDefaults {
  var sortWasChanged = false
  override func setInteger(value: Int, forKey defaultName: String) {
    if defaultName == "sort" {
      sortWasChanged = true
    }
  }
}

MockUserDefaults is a subclass of NSUserDefaults; it has a boolean property sortWasChanged with a default value of false. It also overrides the method setInteger(_:forKey:) that changes the value of sortWasChanged to true.

Add the following test below the last test in your test class:

func testSortingCanBeChanged() {
  // given
  // 1
  let mockUserDefaults = MockUserDefaults(suiteName: "testing")!
  viewController.userDefaults = mockUserDefaults
  
  // when
  // 2
  let segmentedControl = UISegmentedControl()
  segmentedControl.selectedSegmentIndex = 0
  segmentedControl.addTarget(viewController, action: "changeSorting:", forControlEvents: .ValueChanged)
  segmentedControl.sendActionsForControlEvents(.ValueChanged)
  
  // then
  // 3
  XCTAssertTrue(mockUserDefaults.sortWasChanged, "Sort value in user defaults should be altered")
}

Here’s the play-by-play of this test:

  1. You first assign an instance of MockUserDefaults to userDefaults of the view controller; this technique is known as dependency injection).
  2. You then create an instance of UISegmentedControl, add the view controller as the target for the .ValueChanged control event and send the event.
  3. Finally, you assert that setInteger(_:forKey:) of the mock user defaults was called. Note that you don’t check if the value was actually stored in NSUserDefaults, since that’s an implementation detail.

Run your suite of tests — they should all succeed.

What about the case when you have a really complicated API or framework underneath your app, but all you really want to do is test a small feature without delving deep into the framework?

That’s when you “fake” it ’till you make it! :]

Writing Fakes

Fakes behave like a full implementation of the classes they are faking. You use them as stand-ins for classes or structures that are too complicated to deal with for the purposes of your test.

In the case of the sample app, you don’t want to add records to and fetch them from the real Core Data persistent store in your tests. So instead, you’ll fake the Core Data persistent store.

Select the BirthdaysTests folder and go to File\New\File…. Choose an iOS\Source\Test Case Class template and click Next. Name your class it PeopleListDataProviderTests, click Next and then Create.

Again remove the following demo tests in the created test class:

func testExample() {
  // ...
}

func testPerformanceExample() {
  // ...
}

Add the following two imports to your class:

import Birthdays
import CoreData

Now add the following properties:

var storeCoordinator: NSPersistentStoreCoordinator!
var managedObjectContext: NSManagedObjectContext!
var managedObjectModel: NSManagedObjectModel!
var store: NSPersistentStore!

var dataProvider: PeopleListDataProvider!

Those properties contain the major components that are used in the Core Data stack. To get started with Core Data, check out our tutorial, Core Data Tutorial: Getting Started

Add the following code to setUp():

// 1
managedObjectModel = NSManagedObjectModel.mergedModelFromBundles(nil)
storeCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)
store = storeCoordinator.addPersistentStoreWithType(NSInMemoryStoreType, 
  configuration: nil, URL: nil, options: nil, error: nil)

managedObjectContext = NSManagedObjectContext()
managedObjectContext.persistentStoreCoordinator = storeCoordinator

// 2
dataProvider = PeopleListDataProvider()
dataProvider.managedObjectContext = managedObjectContext

Here’s what’s going on in the code above:

  1. setUp() creates a managed object context with an in-memory store. Normally the persistent store of Core Data is a file in the file system of the device. For these tests, you are creating a ‘persistent’ store in the memory of the device.
  2. Then you create an instance of PeopleListDataProvider and the managed object context with the in-memory store is set as its managedObjectContext. This means your new data provider will work like the real one, but without adding or removing objects to the persistent store of the app.

Add the following two properties to PeopleListDataProviderTests:

var tableView: UITableView!
var testRecord: PersonInfo!

Now add the following code to the end of setUp():

let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("PeopleListViewController") as! PeopleListViewController
viewController.dataProvider = dataProvider

tableView = viewController.tableView

testRecord = PersonInfo(firstName: "TestFirstName", lastName: "TestLastName", birthday: NSDate())

This sets up the table view by instantiating the view controller from the storyboard and creates an instance of PersonInfo that will be used in the tests.

When the test is done, you’ll need to discard the managed object context.

Replace tearDown() with the following code:

override func tearDown() {
  managedObjectContext = nil
  
  var error: NSError? = nil
  XCTAssert(storeCoordinator.removePersistentStore(store, error: &error), 
    "couldn't remove persistent store: \(error)")
  
  super.tearDown()
}

This code sets the managedObjectContext to nil to free up memory and removes the persistent store from the store coordinator. This is just basic housekeeping. You want to start each test with a fresh test store.

Now — you can write the actual test! Add the following test to your test class:

func testThatStoreIsSetUp() {
  XCTAssertNotNil(store, "no persistent store")
}

This tests checks that the store is not nil. It’s a good idea to have this check here to fail early in case the store could not be set up.

Run your tests — everything should pass.

The next test will check whether the data source provides the expected number of rows.

Add the following test to the test class:

func testOnePersonInThePersistantStoreResultsInOneRow() {
  dataProvider.addPerson(testRecord)
  
  XCTAssertEqual(tableView.dataSource!.tableView(tableView, numberOfRowsInSection: 0), 1, 
    "After adding one person number of rows is not 1") 
}

First, you add a contact to the test store, then you assert that the number of rows is equal to 1.

Run the tests — they should all succeed.

By creating a fake “persistent” store that never writes to disk, you can keep your tests fast and your disk clean, while maintaining the confidence that when you actually run your app, everything will work as expected.

In a real test suite you could also test the number of sections and rows after adding two or more test contacts; this all depends on the level of confidence you’re attempting to reach in your project.

If you’ve ever worked with several teams at once on a project, you know that not all parts of the project are ready at the same time — but you still need to test your code. But how can you test a part of your code against something that may not exist, such as a web service or other back-end provider?

Stubs to the rescue! :]

Writing Stubs

Stubs fake a response to method calls of an object. You’ll use stubs to test your code against a web service that isn’t yet finished.

The web team for your project has been tasked with building a website with the same functionality of the app. The user creates an account on the website and can then synchronize the data between the app and the website. But the web team hasn’t even started – and you’re nearly done. Looks like you’ll have to write a stub to stand-in for the web backend.

In this section you will focus on two test methods: one for fetching contacts added to the website, and one to post contacts from your app to the website. In a real-world scenario you’d also need some kind of login mechanism and error handling, but that’s beyond the scope of this tutorial.

Open APICommunicatorProtocol.swift; this protocol declares the two methods for getting contacts from the web service and for posting contacts to the web service.

You could pass around Person instances, but this would require you to use another managed object context. Using a struct is simpler in this case.

Now open APICommunicator.swift. APICommunicator conforms to APICommunicatorProtocol, but right now there’s just enough implementation to keep the compiler happy.

You’ll now create stubs to support the interaction of the view controller with an instance of APICommunicator.

Open PeopleListViewControllerTests.swift and add the following class definition within the PeopleListViewControllerTests class:

// 1
class MockAPICommunicator: APICommunicatorProtocol {
  var allPersonInfo = [PersonInfo]()
  var postPersonGotCalled = false
  
  // 2
  func getPeople() -> (NSError?, [PersonInfo]?) {
    return (nil, allPersonInfo)
  }
  
  // 3
  func postPerson(personInfo: PersonInfo) -> NSError? {
    postPersonGotCalled = true
    return nil
  }
}

There are few things to note here:

  1. Even though APICommunicator is a struct, the mock implementation is a class. It’s more convenient to use a class in this case because your tests require you to mutate data. This is a little easier to do in a class than in a struct.
  2. getPeople() returns what is stored in allPersonInfo. Instead of going out on the web and having to download or parse data, you just store contact information in a simple array.
  3. postPerson(_:) sets postPersonGotCalled to true.

You’ve just created your “web API” in under 20 lines of code! :]

Now it’s time to test your stub API by ensuring all contacts that come back from the API are added to the persistent store on the device when you call addPerson().

Add the following test method to PeopleListViewControllerTests:

func testFetchingPeopleFromAPICallsAddPeople() {
  // given
  // 1
  let mockDataProvider = MockDataProvider()
  viewController.dataProvider = mockDataProvider
  
  // 2
  let mockCommunicator = MockAPICommunicator()
  mockCommunicator.allPersonInfo = [PersonInfo(firstName: "firstname", lastName: "lastname", 
    birthday: NSDate())]
  viewController.communicator = mockCommunicator
  
  // when
  viewController.fetchPeopleFromAPI()
  
  // then
  // 3
  XCTAssert(mockDataProvider.addPersonGotCalled, "addPerson should have been called")
}

Here’s what going on in the above code:

  1. First you set up the mock objects mockDataProvider and mockCommunicator you’ll use in the test.
  2. Then you set up some fake contacts and call fetchPeopleFromAPI() to make a fake network call.
  3. Finally you test that addPerson(_:) was called.

Build and run your tests — all should pass.

Where to Go From Here?

Download the final project here; this version also includes some extra tests that didn’t make it into the tutorial.

You’ve learned how to write mocks, fakes and stubs to test micro features in your app, along with getting a sense how XCTest works in Swift.

The tests in this tutorial are only a starter; I’m sure you already have ideas for tests in your own projects.

For more on unit testing, check out Test Driven Development (TDD) and Behavior Driven Development (BDD). Both are development methodologies (and, frankly, a whole new mindset) where you write the tests before you write the code.

You can listen to tutorial team member Ellen Shapiro discuss unit testing in the official Ray Wenderlich podcast.

Unit Tests are only one part of a complete test suite; integration tests are the next logical step. An easy way to start working with integration tests is UIAutomation. It’s well worth the read if you’re serious about testing your apps — and you should be! :]

If you have any comments or questions about this tutorial, feel free to join the discussion in the forum below!