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 3 of 4 of this article. Click here to view the first page.

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.