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

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.