Generate test fakes from Swift code. The fakes allow you to:
- Verify how many times a function was called
- Verify what arguments were received
- Return a canned value
ruby -v
to check your Ruby version is 2.1+brew install sourcekitten
- SourceKitten is used in the Swift parsinggem install swiftfake
Pass a Swift file path and the fake will be printed to STDOUT:
swiftfake ./app/MySwiftClass.swift
You could then pipe the output:
# To clipboard
swiftfake ./app/MySwiftClass.swift | pbcopy
# To a file
swiftfake ./app/MySwiftClass.swift > ./test/FakeMySwiftClass.swift
Subclassing is a marmite solution to faking objects in Swift. Purists may prefer a stubbed implementation of a protocol, allowing a real and fake object to be swapped in a manner transparent to the caller. But in practice subclassing requires less code since no protocol is needed.
If we have a WidgetViewController
which consumes a WidgetService
, we may wish to verify the interactions with & provide a canned response from a FakeWidgetService
, especially if the service has complex business logic.
Here's the WidgetViewController
which calls a WidgetService
instance:
import UIKit
class WidgetViewController: UIViewController {
var widgetService = WidgetService()
var widgets: [Widget]?
override func viewDidLoad() {
super.viewDidLoad()
widgets = widgetService.fetchWidgets(true)
}
}
Here's the interface of the real WidgetService
with complex business logic:
import Foundation
class WidgetService {
func fetchWidgets(onlyBlue: Bool) -> [Widget] {
// ... Complex business logic
return []
}
}
And here's a generated FakeWidgetService
which subclasses the original WidgetService
:
import Foundation
@testable import ExampleApp
internal class FakeWidgetService: WidgetService {
override func fetchWidgets(onlyBlue: Bool) -> [Widget] {
fetchWidgetsCallCount += 1
fetchWidgetsArgsForCall.append(onlyBlue)
return fetchWidgetsReturnValue
}
// MARK: - Fake Helpers
var fetchWidgetsCallCount = 0
var fetchWidgetsArgsForCall = [Bool]()
var fetchWidgetsReturnValue = [Widget]()
}
xCallCount
is a counter for how many times the function has been called.xArgsForCall[n]
stores the arguments received from each call to the function.xReturnValue
is the canned return value which can be set prior to commencing the test.
And this is how you could use the fake in a test:
import XCTest
@testable import ExampleApp
class WidgetViewControllerTests: XCTestCase {
var vc: WidgetViewController!
var fakeWidgetService: FakeWidgetService!
var expectedWidget: Widget!
override func setUp() {
super.setUp()
continueAfterFailure = false
let storyboard = UIStoryboard(name: "Main", bundle: nil)
vc = storyboard.instantiateInitialViewController() as! WidgetViewController
expectedWidget = Widget(name: "Widgetty", color: "Blue")
fakeWidgetService = FakeWidgetService()
fakeWidgetService.fetchWidgetsReturnValue = [expectedWidget]
vc.widgetService = fakeWidgetService // Property based injection of fake onto UIViewController
vc.beginAppearanceTransition(true, animated: false)
vc.endAppearanceTransition()
}
func testVerifyWidgetServiceInteraction() {
XCTAssertEqual(fakeWidgetService.fetchWidgetsCallCount, 1)
XCTAssertEqual(fakeWidgetService.fetchWidgetsArgsForCall[0], true)
guard let loadedWidgets = vc.widgets else {
XCTFail("View controller has no widgets")
return
}
XCTAssertTrue(loadedWidgets[0] == expectedWidget)
}
}
On the way!
This gem is still in an alpha state.
Roadmap:
- Template overrides
- Fake Protocol implementations
- Implement Bright Futures support
- Handling multiple classes/protocols in the Swift source file