Giter Club home page Giter Club logo

mockingbird's Introduction

Mockingbird

Package managers License Slack

Mockingbird is a convenient mocking framework for Swift.

// Mocking
let bird = mock(Bird.self)

// Stubbing
given(bird.getName()) ~> "Ryan"

// Verification
verify(bird.fly()).wasCalled()

Installation

Mockingbird comes in two parts, both of which should be installed:

  1. The Mockingbird Framework provides functions for mocking, stubbing, and verification in tests.
  2. The Mockingbird CLI generates mocks.

CocoaPods

Add the framework to a test target in your Podfile, making sure to include the use_frameworks! option.

target 'ATestTarget' do
  use_frameworks!
  pod 'MockingbirdFramework', '~> 0.7.0'
end

This will download and install the CLI during the next pod install.

Carthage

Add the framework to your Cartfile.

github "birdrides/mockingbird" ~> 0.7.0

And set up Carthage to only build the framework when running carthage update.

$ carthage update --no-build
$ cd Carthage/Checkouts/Mockingbird
$ make bootstrap-carthage

Then download and install the CLI.

$ make install-prebuilt

Swift Package Manager

Add the framework as a package and test target dependency in your Package.swift file.

dependencies: [
  .package(url: "https://github.com/birdrides/mockingbird.git", .upToNextMajor(from: "0.7.0"))
],
targets: [
  .testTarget(
    name: "ATestTarget",
    dependencies: [
      "Mockingbird"
    ]
  )
]

Then download and install the CLI.

$ swift package update
$ cd .build/checkouts/Mockingbird
$ make install-prebuilt

From Source

Clone the repository and build the MockingbirdFramework scheme for the desired platform. Drag the built Mockingbird.framework product into your project and link the library to your test target.

$ git clone https://github.com/birdrides/mockingbird.git
$ cd mockingbird
$ open Mockingbird.xcodeproj

Then build and install the CLI.

$ make install

Setup

Mockingbird generates mocks using the mockingbird command line tool which can be integrated into your build process in many different ways.

Automatic Integration

Use the Mockingbird CLI to set up a destination unit test target. List all source targets that should generate mocks. Below, Mockingbird will mock types in Bird and BirdManagers which can then be used in BirdTests.

$ mockingbird install \
  --targets Bird BirdManagers \
  --destination BirdTests

Manual Integration

Add a Run Script Phase to each target that should generate mocks.

mockingbird generate

By default, Mockingbird will generate target mocks into the $(SRCROOT)/MockingbirdMocks directory. You can specify a custom output location for each target using the outputs CLI option.

Once generated, you must include each .generated.swift mock file as part of your unit test target sources.

Excluding Files

You can exclude unwanted or problematic sources from being mocked by adding a .mockingbird-ignore file. Mockingbird follows the same pattern format as .gitignore and scopes ignore files to their enclosing directory.

Usage

An example demonstrating basic usage of Mockingbird can be found at TreeTests.swift.

Mocking

Mocking lets you create objects which can be passed in place of the original type. Generated mock types are always suffixed with Mock.

/* Bird.swift */
protocol Bird {
  var name: String { get set }
  func canChirp(volume: Int) -> Bool
  func fly()
}

/* Tests.swift */
let bird = mock(Bird.self)  // Returns a `BirdMock`

You can also mock classes that have designated initializers. Keep in mind that class mocks rely on subclassing which has certain limitations, so consider using protocols whenever possible.

/* BirdClass.swift */
class BirdClass {
  let name: String
  init(name: String) {
    self.name = name
  }
}

/* Tests.swift */
let birdClass = mock(BirdClass.self).initialize(name: "Ryan")

Stubbing

Stubbing allows you to define a custom value to return when a mocked method is called.

given(bird.getName()) ~> "Ryan"

You can use an argument matcher when stubbing methods with parameters. Stubs added later have precedence over those added earlier, so stubs containing specific matchers should be added last.

given(bird.canChirp(volume: any())) ~> false    // Matches any volume
given(bird.canChirp(volume: notNil())) ~> true  // Matches any non-nil volume
given(bird.canChirp(volume: 10)) ~> false       // Matches volume = 10

Stub variables with their getter and setter methods.

given(bird.getName()) ~> "Big Bird"
given(bird.setName(any())) ~> { print($0) }

Getters can be stubbed to automatically save and return values.

given(bird.getName()) ~> lastSetValue(initial: "One")
bird.name = "Two"
assert(bird.name == "Two")

It’s possible to stub multiple methods with the same return type in a single call.

given(
  birdOne.getName(),
  birdTwo.getName()
) ~> "Big Bird"

Verification

Verification lets you assert that a mock received a particular invocation during its lifetime.

/* Tree.swift */
class Tree {
  let bird: Bird
  init(with bird: Bird) {
    self.bird = bird
  }

  func shake() {
    bird.fly()
  }
}

/* Tests.swift */
let tree = Tree(with: bird)
tree.shake()  // Shaking the tree should scare the bird away
verify(bird.fly()).wasCalled()

It’s possible to verify that an invocation was called a specific number of times with a count matcher.

verify(bird.fly()).wasNeverCalled()            // n = 0
verify(bird.fly()).wasCalled(exactly(10))      // n = 10
verify(bird.fly()).wasCalled(atLeast(10))      // n ≥ 10
verify(bird.fly()).wasCalled(atMost(10))       // n ≤ 10
verify(bird.fly()).wasCalled(between(5...10))  // 5 ≤ n ≤ 10

Count matchers also support chaining and negation using logical operators.

verify(bird.fly()).wasCalled(not(exactly(10)))           // n ≠ 10
verify(bird.fly()).wasCalled(exactly(10).or(atMost(5)))  // n = 10 || n ≤ 5

Sometimes you need to perform custom checks on received parameters by using an argument captor.

let nameCaptor = ArgumentCaptor<String>()
verify(bird.setName(nameCaptor.matcher)).wasCalled()
assert(nameCaptor.value?.hasPrefix("R"))

You can test asynchronous code by using an eventually block which returns an XCTestExpectation.

DispatchQueue.main.async {
  Tree(with: bird).shake()
}
let expectation = eventually {
  verify(bird.fly()).wasCalled()
  verify(bird.chirp()).wasCalled()
}
wait(for: [expectation], timeout: 1.0)

Verifying doesn’t remove recorded invocations, so it’s safe to call verify multiple times (even if not recommended).

verify(bird.fly()).wasCalled()  // If this succeeds...
verify(bird.fly()).wasCalled()  // ...this also succeeds

For methods overloaded by return type, you should help the compiler by specifying the type returned.

/* Bird.swift */
protocol Bird {
  func getMessage<T>() -> T
  func getMessage() -> String
  func getMessage() -> StaticString
}

/* Tests.swift */
verify(bird.getMessage()).returning(String.self).wasCalled()

Resetting Mocks

Occasionally it’s necessary to remove stubs or clear recorded invocations.

reset(bird)                 // Removes all stubs and recorded invocations
clearStubs(on: bird)        // Only removes stubs
clearInvocations(on: bird)  // Only removes recorded invocations

Argument Matching

Argument matchers allow wildcard matching of arguments during stubbing or verification.

any()                    // Matches any value
any(of: 1, 2, 3)         // Matches any value in {1, 2, 3}
any(where: { $0 > 42 })  // Matches any number greater than 42
notNil()                 // Matches any non-nil value

For methods overloaded by parameter type (such as with generics), using a matcher may cause ambiguity for the compiler. You can help the compiler by specifying an explicit type in the matcher.

any(Int.self)
any(Int.self, of: 1, 2, 3)
any(Int.self, where: { $0 > 42 })
notNil(String?.self)

You can also match elements or keys within collection types.

any(containing: 1, 2, 3)  // Matches any collection with values {1, 2, 3}
any(keys: "a", "b", "c")  // Matches any dictionary with keys {"a", "b", "c"}
any(count: atMost(42))    // Matches any collection with at most 42 elements
notEmpty()                // Matches any non-empty collection

If you provide a concrete instance of an Equatable type, argument values will be compared using equality. Types that don’t conform to Equatable will be compared by reference.

// Many Swift stdlib types such as `String` conform to `Equatable`
verify(bird.setName("Ryan")).wasCalled()

Supporting Source Files

Add supporting source files to mock inherited types defined outside of your project. You should always provide supporting source files when working with system frameworks like UIKit or precompiled external dependencies.

/* MyEquatableProtocol.swift */
protocol MyEquatableProtocol: Equatable {
  // ...
}

/* MockingbirdSupport/Swift/Equatable.swift */
public protocol Equatable {
  static func == (lhs: Self, rhs: Self) -> Bool
}

Setup

Mockingbird includes supporting source files for Foundation, UIKit, and other common system frameworks. For automatic integration, simply copy the MockingbirdSupport folder into your project’s source root.

If you share supporting source files between projects, you can specify a custom --support directory when running the CLI installer or generator.

Structure

Supporting source files should be contained in a directory that matches the module name. You can define submodules and transitive dependencies by nesting directories.

MockingbirdSupport/
├── Foundation/
│   └── ObjectiveC/
│       └── NSObject.swift
└── Swift/
    └── Codable.swift
    └── Comparable.swift
    └── Equatable.swift
    └── Hashable.swift

With the above file structure, NSObject can be imported from both the Foundation and ObjectiveC modules.

Performance

Mockingbird was built to be fast. Its current baseline is under 1 ms per generated mock. See Performance for benchmarks and methodology.

Mockingbird CLI

Generate

Generate mocks for a set of targets in a project.

mockingbird generate

Option Default Value Description
--project (inferred) Path to your project’s .xcodeproj file.
--targets $TARGET_NAME List of target names to generate mocks for.
--srcroot $SRCROOT The folder containing your project’s source files.
--outputs (inferred) List of mock output file paths for each target.
--support (inferred) The folder containing supporting source files.
--condition (none) Compilation condition to wrap all generated mocks in, e.g. DEBUG.
Flag Description
--disable-module-import Omit @testable import <module> from generated mocks.
--only-protocols Only generate mocks for protocols.
--disable-swiftlint Disable all SwiftLint rules in generated mocks.
--disable-cache Ignore cached mock information stored on disk.

Install

Set up a destination (unit test) target.

mockingbird install

Option Default Value Description
--targets (required) List of target names that should generate mocks.
--destination (required) The target name where the Run Script Phase will be installed.
--project (inferred) Your project’s .xcodeproj file.
--srcroot <project>/../ The folder containing your project’s source files.
--outputs (inferred) List of mock output file paths for each target.
--support (inferred) The folder containing supporting source files.
--condition (none) Compilation condition to wrap all generated mocks in, e.g. DEBUG.
--loglevel (none) The log level to use when generating mocks, quiet or verbose
Flag Description
--ignore-existing Don’t overwrite existing Run Scripts created by Mockingbird CLI.
--asynchronous Generate mocks asynchronously in the background when building.
--only-protocols Only generate mocks for protocols.
--disable-swiftlint Disable all SwiftLint rules in generated mocks.
--disable-cache Ignore cached mock information stored on disk.

Uninstall

Remove Mockingbird from a (unit test) target.

mockingbird uninstall

Option Default Value Description
--targets (required) List of target names to uninstall the Run Script Phase.
--project (inferred) Your project’s .xcodeproj file.
--srcroot <project>/../ The folder containing your project’s source files.

Global Options

Flag Description
--verbose Log all errors, warnings, and debug messages.
--quiet Only log error messages.

Inferred Paths

--project

Mockingbird will first check if the environment variable $PROJECT_FILE_PATH was set (usually by an Xcode build context). It will then perform a shallow search of the current working directory for an .xcodeproj file. If multiple .xcodeproj files exist then you must explicitly provide a project file path.

--outputs

By default Mockingbird will generate mocks into the $(SRCROOT)/MockingbirdMocks directory with the file name $(PRODUCT_MODULE_NAME)Mocks.generated.swift.

--support

Mockingbird will recursively look for supporting source files in the $(SRCROOT)/MockingbirdSupport directory.

Resources

mockingbird's People

Contributors

andrewchang-bird avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.