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 .

2.9 (8) · 1 Review

Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

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