Who needs unit tests? Not for you - your code is perfect. But still, you just need to read this article, which should tell more about writing unit tests on Swift. Suddenly it will come in handy later.
Unit testing is a great way to write flawless code; Testing will help you find the most mistakes at an early stage of writing a project. As experience shows: if you have difficulties in testing the code, then you will have difficulties with its support or debugging.
Unit testing works with isolated “microcomponents”. Often you need to “mock” classes - that is, to provide a fake with a functional implementation to isolate a specific microcomponent, so that it can be tested. In
Objective-C, there are several third-party frameworks that help implement this. But they are not yet available in
Swift .
')
In this tutorial, you will learn how to write your own mock objects, fakes, and stubs to cover with a dowry a simple app that helps you remember the birthdays of your friends.
let's startDownload the startup project this is a contact storage application. You will not work on the functionality of the base application; rather, you will write several tests for it to make sure that the application is working properly.
Compile and run the application, and then check how it works. Click the
plus button and then add
John Appleseed to the general contact list:

For storage of contacts, applications uses
Core Data .

Do not panic! You do not need
Core Data experience for this lesson; for this you do not need to have any special skills.
Advantages and disadvantages of unit testingWhen it comes to writing tests, you will come across both good and bad news. The bad news is that unit testing has the following disadvantages:
- Large amount of code: In projects with large test coverage, you may have more tests than functional code.
- More support: The more code, the more it needs to be supported.
- No right decision: Unit testing does not guarantee, and can not guarantee that your code will be error free.
- Takes more time: Writing tests takes some time - the time you could spend studying new information on habrahabr.ru !
Although there is no perfect solution, there is a bright side - writing tests has the following advantages:
- Confidence: You can make sure your code works.
- Quick reviews: You can use unit testing to quickly verify code that is hidden under many layers of navigation — too large components that need to be manually checked.
- Modularity: Unit testing helps you focus on writing more modular code.
- Orientation: Writing tests for micro-components will help you focus on small details.
- Regression: Ensure that the errors you fixed earlier remain corrected — and are not disturbed by subsequent corrections.
- Refactoring: Until Xcode is smart enough to rewrite the code yourself, you will need unit testing to check for refactoring.
- Documentation: Unit testing describes what you think the code should do; he is another way of writing code.
Basic application structureA large amount of code in applications is based on the
Master-Detail Application template with
Core Data enabled. But there are some significant improvements to the code pattern. Open the project in
Xcode and look at the project navigator:

Consider the following details:
- You have a Person.swift file and a PersonInfo.swift file. The Person class is a descendant of NSManagedObject , which contains some basic information about each person. The PersonInfo structure contains the same information, but can be updated from the address book.
- The PeopleList folder has three files: a view controller, a data provider, and a data provider protocol.
A collection of files in
PeopleList to avoid large view controllers. In order to avoid large view controllers, you can shift some responsibilities to other classes that connect to view controllers using a simple protocol. You can learn more about large 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 see. The class that conforms to this protocol must have the properties
managedObjectContext and
tableView , and must define the
addPerson (_ :) and
fetch () methods. In addition, it must comply with the
UITableViewDataSource protocol.
The view controller
PeopleListViewController has a
dataProvider property, which corresponds to the
PeopleListDataProviderProtocol protocol. This property is set to the PeopleListDataProvider instance in the AppDelegate.swift file.
Add new people to your contact list using
ABPeoplePickerNavigationController . This class allows you, as a developer, to have access to user contacts without needing permission.
PeopleListDataProvider is responsible for filling out the table view and accessing
Core Data .
Note: Several classes and methods in the project are declared public; so that the target for tests can access classes and methods. Target for tests is outside the application module. If you do not add an access modifier, classes and methods are defined as
internal . This means that they are only available in the same module. To access them from outside the module (for example, from a test target), you must add a
public access
modifier .
Well, it's time to write a few tests!
Writing mock objectsMock objects allow you to check whether a method call is made or a property is set. For example, on
viewDidLoad () from
PeopleListViewController , the table view is set to the
tableView property of the
dataProvider .
You write a test to check what is actually happening.
Preparation of the application for testingFirst, you need to prepare a project for writing tests.
Select a project in the project navigator, then select
Build Settings in the Birthdays testing tag. Find the
Defines Module , and change the settings to
Yes , as shown below:

Then select the
BirthdaysTests folder and go to
File \ New \ File .... Select
iOS \ Source \ Test Case Class , then click Next, name it
PeopleListViewControllerTests , make sure you create the
Swift file, click
Next again, then click
Create .
If
Xcode prompts you to create a unifying header, select
No. This is an error in
Xcode that occurs when there are no files in the target, and you add a new
Swift file.
Open the newly created
PeopleListViewControllerTests.swift . Import the module you just included 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 methods:
func testExample() {
You now need a copy of
PeopleListViewController , so you can use it in tests.
Add the following line to the top of the
PeopleListViewControllerTests var viewController: PeopleListViewController!
Then replace the setUp () method with the following code:
override func setUp() { super.setUp() viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("PeopleListViewController") as! PeopleListViewController }
It uses the
storyboard to create an instance of
PeopleListViewController , and assigns it to the
viewController .
Select
Product \ Test ;
Xcode compiles the project and runs any existing tests. Although you have no tests yet, this will allow you to make sure that everything is set up correctly. After a few seconds,
Xcode should report that all tests passed successfully.
You are now on your way to creating your first mock object.
Writing the first mock objectSince you are going to work with
Core Data , add the following import up
PeopleListViewControllerTests.swift , immediately after the
import Birthdays line :
import CoreData
Then add the following code to the definition of the
PeopleListViewControllerTests class:
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 rather complicated mock object. However, this is simply the required absolute minimum, since you are going to assign an instance of this mock class to the
PeopleListViewController dataProvider property. Your mock class must also comply with the
PeopleListDataProviderProtocol , as well as the
UITableViewDataSource protocol.
Select
Product \ Test ; the project will be re-compiled, and your tests passed to ur. But now you are all set for the first unit test using a mock object.
It is necessary to divide unit testing into three parts, naming them,
given ,
when , and
then . '
Given ' sets up the environment; '
when ' executes the code you want to test; and then checks the expected result.
Your test will verify that the
tableView property of the
data provider is set after the
viewDidLoad () method
has been executed.
Add the following test to
PeopleListViewControllerTests: func testDataProviderHasTableViewPropertySetAfterLoading() {
Here is how the above test works:
- Creates an instance of MockDataProvider and sets it to the view controller property for dataProvider.
- Confirms that the tableView property is nil before the test begins.
- Has access to the view to run viewDidLoad () .
- Confirms that the property of the test class tableView is not nil and the property is set in the tableView of the view controller.
Then select
Product \ Test again; as soon as the tests are completed, open the navigator (
Cmd + 5 - a convenient shortcut). And you should see the following:

Your first testing with a mock object was successful!
Testing the addPerson (_ :) methodThe next test is to make sure that choosing a contact from the list will call the
addPerson (_ :) method
Add the following property to the
MockDataProvider class:
var addPersonGotCalled = false
Then replace the
addPerson (_ :) method with the following:
func addPerson(personInfo: PersonInfo) { addPersonGotCalled = true }
Now, when you call
addPerson (_ :) , you register it in the instance by setting the value to
true for
MockDataProvider .
You will have to import the
AddressBookUI framework before you can add a method to test this behavior.
Add the following import to
PeopleListViewControllerTests.swift :
import AddressBookUI
Now add the following test method to the rest of the test scripts:
func testCallsAddPersonOfThePeopleDataSourceAfterAddingAPersion() {
So what happens here?
- First you install the data provider of the view controller to an instance of your fake data provider.
- Then you create a contact using ABPersonCreate () .
- Here you manually call the delegate method peoplePickerNavigationController (_: didSelectPerson :) . Typically, manually calling delegate methods is a sign of bad code, but good for testing purposes.
- Finally, you confirm that addPerson (_ :) was called by checking that addPersonGotCalled is true.
Select
Product \ Test to run the tests. So it turns out, this is quite an easy task!
But wait! Do not hurry! How do you know that tests actually test what you think they are testing?
Test your testsA quick way to verify that a test is actually checking something is to remove the object that the test is checking.
Open
PeopleListViewController.swift and comment out the following line
peoplePickerNavigationController (_: didSelectPerson :) :
dataProvider?.addPerson(person)
Run the tests again; The last test you just wrote should now fail. Masterpiece - you know that your tests actually test something. You should check your tests; at the very least, you should check the most complex tests to make sure they work.

Uncomment the line to get the code back into operation; run the tests again to make sure everything works.
Mocking Apple Framework ClassesYou could use singletons such as
NSUserDefaults.standardUserDefaults () and
NSNotificationCenter.defaultCenter () , but how would you test the default value?
Apple does not allow you to check the status of these classes.
You could add a test class as an observer of the expected result. But it can slow down your tests and make them unreliable, since they depend on the implementation of those classes. Or the value could be set from another part of your code, and you did not check the isolated behavior.
To get around these limitations, you can use mock objects instead of these singletons.
Note: When replacing
Apple classes with a mock object, it is very important to test the interaction with that class, and not with the behavior of that class, since implementation details may change at any time.
Compile and run the application; Add
John Appleseed and
David Taylor to the list of people and switch the sorting between
“Last Name” and
“First Name” . You will see that the order of the contacts in the list depends on the sorting.
The code that is responsible for sorting is in the
changeSort () method in
PeopleListViewController.swift :
@IBAction func changeSorting(sender: UISegmentedControl) { userDefaults.setInteger(sender.selectedSegmentIndex, forKey: "sort") dataProvider?.fetch() }
It adds the selected segment index for key sorting to
NSUserDefaults and calls the
fetch () method. The
fetch () method should read this new sort order with
NSUserDefaults and update the contact list shown in
PeopleListDataProvider :
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
NSFetchedResultsController to fetch data from
Core Data . To replace the sorting of the list,
fetch () creates an array using sorting descriptors and sets it in the query query of the selected result controller. It then performs a selection to update the list and call the
reloadData () method for the table.
You will now add a test to make sure that the preferred sorting order of the user is correctly set to
NSUserDefaults .
Open
PeopleListViewControllerTests.swift and add the following class definition below the
Mockdataprovider class
definition :
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 the sortWWCChanged boolean property with a default value of
false . It also overrides the
setInteger (_: forKey :) method , which changes the value of
sortWasChanged to
true .
Add the following test below the last test in the
PeopleListViewControllerTests class:
func testSortingCanBeChanged() {
Here is the report of this verification:
- You first assign an instance of MockUserDefaults to the userDefaults of the view controller; This technique is known as dependency injection.
- Then create an instance of UISegmentedControl , add the view controller as a termset for .ValueChanged .
- Finally, you confirm that setInteger (_: forKey :) the default user mock object was invoked. Notice that you check if the value was actually stored in NSUserDefaults .
Run your test suite — they should all complete successfully.
As for the case when you have a really complicated API or framework, but you really want to test a small component, don't “dig in” deep into the framework!
That's when you “fake” it, not create it! :]
Writing Fakes ObjectsFakes objects behave like the full implementation of the classes they fake. You use them as substitutes for classes or structures that are too difficult to work with.
In the case of an application, you do not need to add entries and select them from
Core Data . So instead you fake
Core Data . Sounds a little scary, doesn't it?
Select the
BirthdaysTests folder and go to
File \ New \ File .... Select the
iOS \ Source \ Test Case Class template and click
Next . Name your class
PeopleListDataProviderTests , click
Next and then
Create .
Again, remove unnecessary tests in the test class you created:
func testExample() {
Add the following two imports to the new class:
import Birthdays import CoreData
Then add the following properties:
var storeCoordinator: NSPersistentStoreCoordinator! var managedObjectContext: NSManagedObjectContext! var managedObjectModel: NSManagedObjectModel! var store: NSPersistentStore! var dataProvider: PeopleListDataProvider!
Properties contain the main components that are used in the
Core Data stack. To get started with
Core Data , see our
Core Data Tutorial: Getting Started.Add the following code to the
setUp () method:
Here is what happens in the above code:
- setUp () creates a managed object context using in-memory storage. Typically, Core Data is a file in the device file system. For these tests, you create a 'permanent' storage in the device's memory.
- Then you create an instance of PeopleListDataProvider and a managed object context with an in-memory store that is set as a managedObjectContext. This means that your new data provider will work as real, but will not add or delete objects in Core Data.
Add the following two properties to
PeopleListDataProviderTests :
var tableView: UITableView! var testRecord: PersonInfo!
Now add the following code to the end of the
setUp () method:
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 configures 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 completed, you will need to “reset” the managed object context.
Replace the
tearDown () method
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 remove the persistent store from the store coordinator. You can run each test with a new test repository.
Now - you can write a test! Add the following test to your test class:
func testThatStoreIsSetUp() { XCTAssertNotNil(store, "no persistent store") }
This test verifies that the store is not nil. Run a new test - everything should be successful.
The following test will check if 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 add the contact to the test repository, then confirm that the number of rows is 1.
Run the tests - they should all succeed.
By creating a fake “permanent” storage, you ensure fast testing and allow the disk to remain clean, so you can be sure that the application will run when you start, as expected.
Written test You can also check the number of sections and lines after you have added two or more test contacts; it all depends on the level of confidence you are trying to achieve in the project.
If you have ever worked with several teams on a project at once, you know that not all parts of the project are ready at the same time, but you already need to test your code. But how can you test part of your code for something that does not exist, for example a web service?
Stub'y come to the rescue!
Writing StubsThe stubs forge the response to the object's method calls. You will use stubs to test your code calling a web service, which may be still in development.
The web team for your project was tasked with creating a web service with the same functionality as the application. The user creates an account on the service and can then synchronize data between the application and the service. But the web team didn’t even start their part of the work, and you’ve almost finished the development. Looks like you should write a stub to replace the web server component.
In this section, you will focus on writing tests of two methods: one for selecting contacts added to the site and one to add contacts from your application to the web service. In the real scenario, you will need a login and account and error handling, but we will do this another time.
Open
APICommunicatorProtocol.swift ; This protocol declares two methods for getting contacts from a web service and adding contacts.
You could move instances of Person, but this would require a different managed object context. Using structures has become much easier in this case.
You will now create stubs to support the interaction of the view controller with the
APICommunicator instance.
Open
PeopleListViewControllerTests.swift and add the following class definition within the
PeopleListViewControllerTests class:
Something needs to be noted:
- Even if the APICommunicator is a structure, the simulated implementation is a class. In this case, it will be more convenient to use the class, because your tests require you to modify the data. This is a little easier to do in class than in structure.
- The getPeople () method returns what is stored in allPersonInfo. Instead of downloading data from the network, you simply store the contact information in a simple array.
- The postPerson (_ :) method sets the postPersonGotCalled value to true.
Now it's time to test your Stub API to make sure all contacts returned from the API are added to the repository when you call the
addPerson () method
Add the following test method to
PeopleListViewControllerTests :
func testFetchingPeopleFromAPICallsAddPeople() {
Here is what happens in the above code:
- First, you configure the mockDataProvider and mockCommunicator imitation objects that you will use in the test.
- Then you set up some fake contacts and call the fetchPeopleFromAPI () method to make a fake network call.
- Finally test the addPerson (_ :) method.
Compile and run the tests.
And what's next?Download the final version of the project ; this version also includes some additional tests that were not covered in this article.
You learned how to write mock objects, fakes and stubs for testing microcomponents in your application and figured out how
XCTest works in
Swift .
This article presents only an initial understanding of the tests; I'm sure you already have ideas for writing tests for your applications.
For more unit testing information, see
Test Driven Development (TDD) and
Behavior Driven Development (BDD) . These are application development methodologies (and, frankly, represent a completely new way of thinking) where you write tests before you write code.
Unit testing is only one part of a complete test suite; comprehensive testing is the next logical step. A simple way to get started with integrated testing is to use
UIAutomation . If you are serious about testing your applications, then you need to use
UIAutomation !
ps Since the article was written earlier 09/09/2015, Swift 1.2 was used to write the examples. I made some changes to the examples due to the release of the new Swift language version. Source code for projects can be found
here and
here .