A mocking framework for Swift

Related tags

Testing SwiftMock
Overview

SwiftMock

SwiftMock is a mocking framework for Swift 5.2.

Notes on the history of this repo

  • September 2015: first version of this framework
  • November 2016: Marked the project as "unmaintained", with a comment "just write fakes instead"
  • November 2018: Rewrote this for Swift 4.2, with much simpler code
  • May 2020: Minor changes

I spent a while using fakes (test-doubles which implement a prototol and simply set various methodWasCalled flags), but this doesn't scale well. It's easy to forget to make assertions, especially if a new function is added to a protocol long after the protocol's fake was written. I've since migrated a lot of code to using this new Mock, and it's amazing how many defects I've found. Mocks FTW!

Versioning

SwiftMock versions track the major/minor version of Swift itself, so you can easily find a tag for a version of SwiftMock which works with your version of Swift.

Limitations

  • Developers need to be aware of the difference between calls which should be mocked, and those which shouldn't (ie, simple stubs)
    • if the function in the collabotor class performs an operation (starting a network request, logging-out a user, starting a timer), then it's good for mocking
    • if the function returns a value which your system-under-test uses to make a decision, and that decision is asserted elsewhere in your test code, and the function doesn't have any side-effects, then that function is not a suitable candidate for mocking
  • No built-in support for stubbing calls. (This is calls which return a given value, but their use isn't asserted by the mock object)
  • You may sometimes need to customise the arguments which are passed into the accept call
  • Not all test-failure scenarios can report exactly where the failure occurred
  • It's possible for calls to get confused if a mock has two functions with the same name and similar arguments - but that seems unlikely to me. (Example: object.function(42) and object.function("42"))

The decision whether to "mock or stub" depends on what the function does. As an example, these two functions have similar method signatures:

  • func isButtonEnabled() -> Bool
  • func saveValuesToKeychain() -> Bool

Both have no arguments, and both return a Bool - but they are very different:

  • isButtonEnabled() returns a boolean based on some internal logic. Your system-under-test will probably take that boolean value and use it to do something else, like calling self.button.setEnabled(enabled) - so your test would probably assert the state of the button is correct, and there's no need to check that isButtonEnabled() is called. We care that the outcome is correct, not how the system-under-test decided to do the correct thing. This doesn't need to be mocked.
  • saveValuesToKeychain() is a command; our system-under-test is asking the mocked collaborator to do some useful work. In this case, we really do care that the function is called, so it should be mocked.

Usage

The examples below assume we're mocking this protocol:

protocol Frood {
    func voidFunction(value: Int)
    func functionReturningString() -> String
    func performAction(value: String, completion: @escaping () -> Void)
}

In your test target you'll need to create a MockFrood which extends Mock with a generic type parameter Frood. It must also adopt the Frood protocol.

class MockFrood: Mock<Frood>, Frood {
    func voidFunction(value: Int) {
        accept(args: [value])
    }

    func functionReturningString() -> String {
        return accept() as! String
    }

    func performAction(value: String, completion: @escaping () -> Void) {
        accept(checkArgs: [value], actionArgs: [completion])
    }
}

Then create the mock in your test class, using MockFrood.create(). A test class would typically look something like this:

class MyTests: XCTestCase {
    private let mockFrood = MockFrood.create()
    
    private func verify(file: StaticString = #file,
                        line: UInt = #line) {
        mockFrood.verify(file: file, line: line)
    }
    
    func test_something() {
        // given
        let thing = MyThing(frood: mockFrood)
        
        // expect
        mockFrood.expect { f in f.voidFunction(value: 42) }
        
        // when
        thing.hoopy()
        
        // then
        verify()
    }

This gives you the following behaviour:

  • verify() will fail the test if voidFunction() wasn't called exactly once with the value 42
  • the mock will fast-fail the test if any other (unexpected) function is called on the mock

The original version of SwiftMock had explicit matcher classes for various types; this newer version simply converts each argument to a String, and matches on those String arguments. You can often simply accept the arguments themseves, but sometimes you'll want to be more specific about what you pass into the accept function.

I'd probably put the mock objects in a separate group in the test part of my project.

Currently-supported syntax

SwiftMock syntax requires the expected call in a block; this might look weird at first, but means that we have a readable way of setting expectations, and we know the return value before the expectation is set.

// expect a call on a void function
mockObject.expect { o in o.voidFunction(42) }
...
mockObject.verify()
// expect a call on a function which returns a String value
mockObject.expect { o in o.functionReturningString() }.returning("dent")
...
mockObject.verify()
// expect a call on a function which returns a String value, and also call a block
mockObject.expect { o in
        o.functionReturningString()
    }.doing { actionArgs in
        print("frood")
    }.returning("dent")
...
mockObject.verify()

Mocks are strict. This means they will reject any unexpected call. If this annoys you, then perhaps you should be stubbing those calls instead of mocking?

Various ways to call the "accept" function when writing your Mock object

// simplest use-case - mocking a function which takes no arguments and
// returns no value

class Mock<Protocol>, Protocol {
    func myFunc() {
        accept()
    }
}
// mocking a function which returns a value
// this uses a force-cast, but in test code I guess we can live with it

class Mock<Protocol>, Protocol {
    func myFunc() -> String {
        return accept() as! String
    }
}
// mocking a function which takes some arguments which must match the
// expected values. SwiftMock will convert each argument to a String
// and match them all

class Mock<Protocol>, Protocol {
    func myFunc(personName: String, yearOfBirth: Int) {
        accept(args: [personName, yearOfBirth])
    }
}
// mocking a function which takes parameter with a custom type

struct Employee {
    let personID: UUID
    let personName: String
    let roles: [Role]
}

class Mock<Protocol>, Protocol {
    func myFunc(employee: Employee) {
        // calling accept(arg) here might not work well - so you can
        // select the important (identifying) parts of the struct
        accept(args: [employee.personID, employee.personName])
    }
}
// specifying the function name explicitly
// SwiftMock's accept call has a func parameter with a default value of
// #func. This works most of the time, but you may wish to override it

class Mock<Protocol>, Protocol {
    func myFunc(value: Int) {
        accept(func: "functionName", args: [value])
    }
}
// arguments which are unknown - example: a completion block which should be captured

// here, the "url" argument is known by the test (and we expect it to be correct),
// but the "completion" block argument must be captured by the test. So "checkArgs"
// are used to check the function was called with the expected parameters, while
// "actionArgs" are ignored by the matching code, and passed into any "doing" block

class Mock<Protocol>, Protocol {
    func getWebContent(url: URL, completion: (Data) -> Void) -> RequestState {
        return accept(checkArgs: [url], actionArgs:[completion]) as! RequestState
    }
}


// usage in the test class

var capturedCompletionBlock: ((Data) -> Void)?

myMock.expect { m in
        m.getWebContent(url: expectedURL, completion: { data in })
    }.doing { actionArgs in
        capturedCompletionBlock = actionArgs[0] as? (Data) -> Void
    }.returning(.requesting)


// then later, call that captured block to simulate an incoming response
capturedCompletionBlock?(response)

Installation

The code is all in one file - so the easiest way to use SwiftMock is to simply copy Mock.swift into your project.

Feedback

Issues and pull-requests most welcome.

Author

Matthew Flint, [email protected]

License

SwiftMock is available under the MIT license. See the LICENSE file for more info.

Comments
  • It does not support @objc objects

    It does not support @objc objects

    The class which inherit from NSObject gets failed because objects are not equal. For example class CustomModel: NSObject {} class MyViewModel { func validateBiometric() { repo.validateCustomerBiometric(requestModel: CustomModel(), success: {}, failure: {} ) } } //TestFile mockRepo.expect { o in o.validateCustomerBiometric(requestModel: CustomModel()) { () in } failure: { () in } }.doing { (actionArgs) in self.capturedCompletionBlock = actionArgs[0] as? (() -> Void) }.returning(())

    viewModel.validateBiometric()

    // verify()

    opened by RohitNegi 7
  • doing block is not working in multiple expectation

    doing block is not working in multiple expectation

    `func test_getYearsFilter_if() {

        //given
        var capturedCompletionBlock: ((YearsFilterResponse) -> Void)?
        let yearsFilterResponseObject = YearsFilterResponse()
        yearsFilterResponseObject.years = ["2020", "2019", "2018"]
        let  statementFilterModel =  [StatementFilterModel]()
        //expect
    
        mockMFStatementRepo.expect { m in
            m.getYearsFilter { (_) in } failure: { (_) in }
        }.doing { (actionArgs) in
            capturedCompletionBlock = actionArgs[0] as? ((YearsFilterResponse) -> Void)
        }
        // expect
        mockMFStatementRepo.expect { o in
            o.getMfStatementFilter()
        }.returning(statementFilterModel)
    
        //when
        viewModel.getYearsFilter()
        //then
        mockMFStatementRepo.verify()
        // then later, call that captured block to simulate an incoming response
        capturedCompletionBlock?(yearsFilterResponseObject)
    
        //Assert
        XCTAssertEqual(viewModel.yearsFilterOptions, yearsFilterResponseObject.years)
    
    }`
    
    opened by RohitNegi 4
  • If two methods have same signature, it fails

    If two methods have same signature, it fails

    If two methods have same signature but different method name, it is failing because it try to match with method mentioned top in the hierarchy. Screenshot 2021-07-27 at 6 13 59 PM

    for example

        func test_void_function() {
            //given
            viewModel = FroodViewModel(delegate: mockFrood)
            //expect
            mockFrood.expect { (f) in
                f.voidFunction(value: 11)
            }
            //when
            viewModel.checkVoidFunction(val: 11)
            //then
            verify()
        }
        
        func test_void_function1() {
            //given
            viewModel = FroodViewModel(delegate: mockFrood)
            //expect
            mockFrood.expect { (f) in
                f.voidFunction1(value: 11)
            }
            //when
            viewModel.checkVoidFunction(val: 11)
            //then
            verify()
        }
    
    opened by RohitNegi 2
  • install SwiftMock by CocoaPods?

    install SwiftMock by CocoaPods?

    -> SwiftMock (0.0.3) [DEPRECATED] A mocking framework for Swift 2.0 pod 'SwiftMock', '~> 0.0.3'

    • Homepage: https://github.com/mflint/SwiftMock
    • Source: https://github.com/mflint/SwiftMock.git
    • Versions: 0.0.3, 0.0.2, 0.0.1 [cocoapods repo]
    opened by ShenYj 1
  • "Reason: image not found" error

    Created a new XCode project (which can be found here - https://github.com/AJ9/SwiftMocking) and get the following error:

    dyld: Library not loaded: @rpath/XCTest.framework/XCTest
    Referenced from: /Users/adamgask/Library/Developer/Xcode/DerivedData/SwiftMocking-glcapzbexukycqbslarjltbruahd/Build/Products/Debug-iphonesimulator/SwiftMock.framework/SwiftMock
    Reason: image not found
    

    Steps:

    • New XCode project
    • pod init

    pod file looks like so:

    # Uncomment this line to define a global platform for your project
    # platform :ios, '6.0'
    use_frameworks!
    
    target 'SwiftMocking' do
    pod "SwiftMock"
    end
    
    target 'SwiftMockingTests' do
    pod "SwiftMock"
    end
    
    target 'SwiftMockingUITests' do
    pod "SwiftMock"
    end
    
    • run the application
    opened by AJ9 1
  • Feature comparison with OCMock

    Feature comparison with OCMock

    I'd love to test my code with Swift. I want to know whether SwiftMock can do stubbing, test spies (partial mocks) etc. and other things that I can do with OCMock before even attempting to pick up and use SwiftMock.

    What'd be nice is a table of like-for-like syntax comparison with OCMock. Not only will that demonstrate what's missing, but also it'll serve as a cheatsheet for those who want to translate their tests to Swift.

    opened by fatuhoku 0
Owner
Matthew Flint
I'm mainly working over at GitLab - only here at GitHub for some forks, and to sponsor some great devs.
Matthew Flint
A convenient mocking framework for Swift

// Mocking let bird = mock(Bird.self) // Stubbing given(bird.getName()).willReturn("Ryan") // Verification verify(bird.fly()).wasCalled() What is Mo

Bird Rides, Inc 545 Jan 5, 2023
A mocking framework for Swift

SwiftMock SwiftMock is a mocking framework for Swift 5.2. Notes on the history of this repo September 2015: first version of this framework November 2

Matthew Flint 257 Dec 14, 2022
Freezer is a library that allows your Swift tests to travel through time by mocking NSDate class.

Freezer Freezer is a library that allows your Swift tests to travel through time by mocking NSDate class. Usage Once Freezer.start() has been invoked,

Sergey Petrov 8 Sep 24, 2022
Efs - Easy function mocking/stubbing in Swift

Efs Efs, as the pronounced plural of letter F (for function), is a simple mockin

Jérémy Touzy 0 Feb 12, 2022
Mockingbird was designed to simplify software testing, by easily mocking any system using HTTP/HTTPS

Mockingbird Mockingbird was designed to simplify software testing, by easily mocking any system using HTTP/HTTPS, allowing a team to test and develop

FARFETCH 183 Dec 24, 2022
Library for unifying the approach to network mocking in iOS unit- & UI-tests.

TinkoffMockStrapping Example To run the example project, clone the repo, and run pod install from the Example directory first. Requirements Installati

Online financial ecosystem 22 Jan 3, 2023
A Matcher Framework for Swift and Objective-C

Nimble Use Nimble to express the expected outcomes of Swift or Objective-C expressions. Inspired by Cedar. // Swift expect(1 + 1).to(equal(2)) expect(

Quick 4.6k Dec 31, 2022
The Swift (and Objective-C) testing framework.

Quick is a behavior-driven development framework for Swift and Objective-C. Inspired by RSpec, Specta, and Ginkgo. // Swift import Quick import Nimbl

Quick 9.6k Dec 31, 2022
BDD Framework and test runner for Swift projects and playgrounds

Spectre Special Executive for Command-line Test Running and Execution. A behavior-driven development (BDD) framework and test runner for Swift project

Kyle Fuller 392 Jan 1, 2023
AutoMocker is a Swift framework that leverages the type system to let you easily create mocked instances of your data types.

AutoMocker Context AutoMocker is a Swift framework that leverages the type system to let you easily create mocked instances of your data types. Here's

Vincent Pradeilles 39 May 19, 2022
BDD-style framework for Swift

Sleipnir Sleipnir is a BDD-style framework for Swift. Sleipnir is highly inspired by Cedar. Also In Norse mythology, Sleipnir is Odin's steed, is the

Railsware 846 Nov 22, 2022
Swift Framework for TestRail's API

QuizTrain ?? ?? QuizTrain is a framework created at Venmo allowing you to interact with TestRail's API using Swift. It supports iOS, macOS, tvOS, and

Venmo 18 Mar 17, 2022
Swift framework containing a set of helpful XCTest extensions for writing UI automation tests

AutoMate • AppBuddy • Templates • ModelGenie AutoMate AutoMate is a Swift framework containing a set of helpful XCTest extensions for writing UI autom

PGS Software 274 Dec 30, 2022
A simple and lightweight matching library for XCTest framework.

Match A simple and lightweight matching library for XCTest framework. Getting started Swift Package Manager You can add Match to your project by addin

Michał Tynior 6 Oct 23, 2022
Genything is a framework for random testing of a program properties.

Genything is a framework for random testing of a program properties. It provides way to random data based on simple and complex types.

Just Eat Takeaway.com 25 Jun 13, 2022
A light-weight TDD / BDD framework for Objective-C & Cocoa

Specta A light-weight TDD / BDD framework for Objective-C. FEATURES An Objective-C RSpec-like BDD DSL Quick and easy set up Built on top of XCTest Exc

Specta / Expecta 2.3k Dec 20, 2022
Switchboard - easy and super light weight A/B testing for your mobile iPhone or android app. This mobile A/B testing framework allows you with minimal servers to run large amounts of mobile users.

Switchboard - easy A/B testing for your mobile app What it does Switchboard is a simple way to remote control your mobile application even after you'v

Keepsafe 287 Nov 19, 2022
Remote configuration and A/B Testing framework for iOS

MSActiveConfig v1.0.1 Remote configuration and A/B Testing framework for iOS. Documentation available online. MSActiveConfig at a glance One of the bi

Elevate 78 Jan 13, 2021
AB testing framework for iOS

ABKit Split Testing for Swift. ABKit is a library for implementing a simple Split Test that: Doesn't require an HTTP client written in Pure Swift Inst

Recruit Marketing Partners Co.,Ltd 113 Nov 11, 2022