I am the author of iOS 17 Fundamentals, Building iOS User Interfaces with SwiftUI, and eight other courses on Pluralsight.
Deepen your understanding by watching!
How to Create Mocks and Stubs in Swift
Without 100% support for a mocking framework like OCMock, I found myself needing to get creative when building mock objects and method stubs in Swift unit tests. The great thing about testing is that you’re…well… testing things out to see if they’ll work, and I found a solution that I’m pretty happy with for now. I’m open to better ways, so leave a comment if you’ve had good results using a different design!
The process is essentially this (example to follow):
- Ensure that the class that you would like to test is designed so that you can substitute your mock for the real one that’s used in your class’ implementation
- Create an
XCTestCase
class with a test function in your unit test project - Within the function body create a nested class
- Make the nested class inherit from the real object you’re trying to mock / create a method stub for
- You can give the nested class a name such as Mock[ObjectName]
- Configure the mock object however you need by setting its properties or overriding its function implementations with stubbed implementations – no need to override every function… only the one(s) that your class calls during the test at hand
- Instantiate the class you’re testing and pass in an instance of the mock object you just nested in the test function to your class somehow (either through its initializer, by setting a property on the class, or by passing it into the method under test via parameter — however you intended to ‘inject’ the mock from step 1 is what you should do)
- XCTAssert…
Let’s see those 8 steps in action for those of us who are more visually inclined.
EDIT: July 22, 2014 – I’ve added a simple Xcode Project to GitHub for those interested in seeing the setup directly in Xcode at https://github.com/andrewcbancroft/MocksAndStubs
The scenario that I’d like to use a mock class in is this: I have a CoreData application and I’d like to be able to mock the NSManagedObjectContext
so that instead of making actual database fetch requests, I can just provide stubs of various sorts with the kinds of responses I’d expect from the real database calls to ensure my class will do the right thing based on predictable results. To do this I begin at step 1…
1. Ensure that the class that you would like to test is designed so that you can substitute your mock for the real one that’s used in your class’ implementation
In the example class below, I intend to provide the NSManagedObjectContext
dependency through the class’ initializer which will set a property that is used by my class’ methods later on, but you could easily use some other way of performing “dependency injection”. The initializer strategy just makes it super clear in my mind what the class’ dependencies are, so that’s what I’m going to do here. Have a look:
1import Foundation
2import CoreData
3
4class MyClass {
5 let context: NSManagedObjectContext
6
7 init(managedObjectContext: NSManagedObjectContext) {
8 self.context = managedObjectContext
9 }
10}
Now, let’s say that my example class has a member function called databaseHasRecordsForSomeEntity
that returns a Bool
value of true if the resulting array of a fetch request contains objects, and a Bool
value of false if the result array of a fetch request is empty. The completed class looks like this:
1import Foundation
2import CoreData
3
4class MyClass {
5 let context: NSManagedObjectContext
6
7 init(managedObjectContext: NSManagedObjectContext) {
8 self.context = managedObjectContext
9 }
10
11 // If the array returned from executing a fetch request contains objects, return true; if empty, return false
12 func databaseHasRecordsForSomeEntity() -> Bool {
13 let fetchRequest = NSFetchRequest(entityName: "SomeEntity")
14 let fetchRequestResults = self.context.executeFetchRequest(fetchRequest, error: nil) // May want to do something with the error in real life...
15 return (fetchRequestResults?.count > 0)
16 }
17}
I want to test if databaseHasRecordsForSomeEntity
does what I intend it to do. So…
2. Create an XCTestCase
class with a test function in your unit test project
Just listing this for completeness
Next comes the way to make the mock. Read steps 3-5 and then look below for a code example of what the skeleton will look like.
3. Within the function body create a nested class
4. Make the nested class inherit from the real object you’re trying to mock / create a method stub for
5. You can give the nested class a name such as Mock[ObjectName]
1import UIKit
2import XCTest
3import CoreData // <-- Make sure to import CoreData or you will get errors when you try to use NSManagedObjectContext
4
5class MyClassTests: XCTestCase {
6
7 override func setUp() {
8 super.setUp()
9 // Put setup code here. This method is called before the invocation of each test method in the class.
10 }
11
12 override func tearDown() {
13 // Put teardown code here. This method is called after the invocation of each test method in the class.
14 super.tearDown()
15 }
16
17 // Yay for verbose test names! :]
18 func testDatabaseHasRecordsForSomeEntityReturnsTrueWhenFetchRequestReturnsNonEmptyArray() {
19 class MockNSManagedObjectContext: NSManagedObjectContext {
20
21 }
22 }
23}
6. Configure the mock object however you need by setting its properties or overriding its function implementations with stubbed implementations – no need to override every function… only the one(s) that your class calls during the test at hand
For my example, I’m going to stub out the executeFetchRequest
method so that it returns an array with one object in it. This is really the part where you have to determine what you’re testing and what you expect the stubbed results to be. Whatever you decide, the way to stub a method is simply to override it in the mock you’re implementing. Here’s how I implemented the executeFetchRequest
stub for my example:
1// Yay for verbose test names! :]
2 func testDatabaseHasRecordsForSomeEntityReturnsTrueWhenFetchRequestReturnsNonEmptyArray() {
3 class MockNSManagedObjectContext: NSManagedObjectContext {
4 override func executeFetchRequest(request: NSFetchRequest, error: NSErrorPointer) -> [AnyObject]? {
5 return ["object 1"]
6 }
7 }
8 }
We’re ready to perform the test and assert the results. Read steps 7-8 and take a look at the code example below step 8:
7. Instantiate the class you’re testing and pass in an instance of the mock object you just nested in the test function to your class somehow (either through its initializer, by setting a property on the class, or by passing it into the method under test via parameter — however you intended to ‘inject’ the mock from step 1 is what you should do)
8. XCTAssert…
From step 1, I intended to pass an NSManagedObjectContext instance to the initializer of MyClass, so that’s what I’ll do in my test. I’ll then perform the XCTAssert on the return value of my method under test:
1// Yay for verbose test names! :]
2 func testDatabaseHasRecordsForSomeEntityReturnsTrueWhenFetchRequestReturnsNonEmptyArray() {
3 class MockNSManagedObjectContext: NSManagedObjectContext {
4 override func executeFetchRequest(request: NSFetchRequest, error: NSErrorPointer) -> [AnyObject]? {
5 return ["object 1"]
6 }
7 }
8
9 // Instantiate mock
10 let mockContext = MockNSManagedObjectContext()
11
12 // Instantiate class under test and pass it the mockContext object
13 let myClassInstance = MyClass(managedObjectContext: mockContext)
14
15 // Call the method under test and store its return value for XCTAssert
16 let returnValue = myClassInstance.databaseHasRecordsForSomeEntity()
17
18 XCTAssertTrue(returnValue == true, "The return value should be been true")
19 }
Running the tests at this point should produce a passing test using the mock object in place of a real NSManagedObjectContext that calls a database!
Now, if I wanted to test the “false” branch of my class’ method, I could simply create another test method following the same steps, only this time, I’d provide a new implementation for the overridden executeFetchRequest
method that’s appropriate:
1func testDatabaseHasRecordsForSomeEntityReturnsFalseWhenFetchRequestReturnsEMPTYArray() {
2 class MockNSManagedObjectContext: NSManagedObjectContext {
3 override func executeFetchRequest(request: NSFetchRequest, error: NSErrorPointer) -> [AnyObject]? {
4 return [] // Provided a different stub implementation to test the "false" branch of my method under test
5 }
6 }
7
8 // Instantiate mock
9 let mockContext = MockNSManagedObjectContext()
10
11 // Instantiate class under test
12 let myClassInstance = MyClass(managedObjectContext: mockContext)
13
14 // Call the method under test and store its return value for XCTAssert
15 let returnValue = myClassInstance.databaseHasRecordsForSomeEntity()
16
17 XCTAssertTrue(returnValue == false, "The return value should be been false")
18 }
And that’s a wrap – happy mocking and stubbing in Swift!
EDIT: July 22, 2014 – I’ve added a simple Xcode Project to GitHub for those interested in seeing the setup directly in Xcode at https://github.com/andrewcbancroft/MocksAndStubs
comments powered by Disqus