Project

swiftfake

0.0
Repository is archived
No commit activity in last 3 years
No release in over 3 years
Generate test fakes from Swift code.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 1.11
~> 10.0
~> 3.0
 Project Readme

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