Skip to content
This repository has been archived by the owner. It is now read-only.
Generate test fakes from Swift code
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
ExampleApp
bin
exe
lib
spec
.gitignore
.rspec
.travis.yml
Gemfile
README.md
Rakefile
swiftfake.gemspec

README.md

Swiftfake

Gem Version

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

Installation

  • ruby -v to check your Ruby version is 2.1+
  • brew install sourcekitten - SourceKitten is used in the Swift parsing
  • gem install swiftfake

Creating fakes

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

Using the fakes via subclassing

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.

Subject under test

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)
    }

}

Real object

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 []
    }

}

Fake object

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.

Using the fake in a 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)
    }

}

Using the fakes via protocols

On the way!

Notes

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
You can’t perform that action at this time.