Mockingbird lets you mock, stub, and verify objects written in either Swift or Objective-C. The syntax takes inspiration from (OC)Mockito but was designed to be “Swifty” in terms of type safety and expressiveness.
// Mocking
let bird = mock(Bird.self)
// Stubbing
given(bird.name).willReturn("Ryan")
// Verification
verify(bird.fly()).wasCalled()
Mockingbird was built to reduce the number of “artisanal” hand-written mocks and make it easier to write tests at Bird. Conceptually, Mockingbird uses codegen to statically mock Swift types and NSProxy
to dynamically mock Objective-C types. The approach is similar to other automatic Swift mocking frameworks and is unlikely to change due to Swift’s limited runtime introspection capabilities.
That said, there are a few key differences from other frameworks:
- Generating mocks takes seconds instead of minutes on large codebases with thousands of mocked types.
- Stubbing and verification failures appear inline and don’t abort the entire test run.
- Production code is kept separate from tests and never modified with annotations.
- Xcode projects can be used as the source of truth to automatically determine source files.
See a detailed feature comparison table and known limitations.
Mockingbird powers thousands of tests at companies including Meta, Amazon, Twilio, and Bird. Using Mockingbird to improve your testing workflow? Consider dropping us a line on the #mockingbird Slack channel.
Let’s say we wanted to test a Person
class with a function that takes in a Bird
.
protocol Bird {
var canFly: Bool { get }
func fly()
}
class Person {
func release(_ bird: Bird) {
guard bird.canFly else { return }
bird.fly()
}
}
With Mockingbird, it’s easy to stub return values and verify that mocked methods were called.
// Given a bird that can fly
let bird = mock(Bird.self)
given(bird.canFly).willReturn(true)
// When a person releases the bird
Person().release(bird)
// Then the bird flies away
verify(bird.fly()).wasCalled()
Select your preferred dependency manager below to get started.
CocoaPods
Add the framework to a test target in your Podfile
, making sure to include the use_frameworks!
option.
target 'MyAppTests' do
use_frameworks!
pod 'MockingbirdFramework', '~> 0.18'
end
In your project directory, initialize the pod.
$ pod install
Finally, configure a test target to generate mocks for each listed source module. This adds a build phase to the test target which calls mockingbird generate
. For advanced usages, modify the installed build phase or set up targets manually.
$ Pods/MockingbirdFramework/mockingbird install --target MyAppTests --sources MyApp MyLibrary1 MyLibrary2
Optional but recommended:
- Exclude generated files from source control
- Add supporting source files for compatibility with external dependencies
Have questions or issues?
Carthage
Add the framework to your Cartfile
.
github "birdrides/mockingbird" ~> 0.18
In your project directory, build the framework and link it to your test target.
$ carthage update --use-xcframeworks
Finally, configure a test target to generate mocks for each listed source module. This adds a build phase to the test target which calls mockingbird generate
. For advanced usages, modify the installed build phase or set up targets manually.
$ mockingbird install --target MyAppTests --sources MyApp MyLibrary1 MyLibrary2
Optional but recommended:
- Exclude generated files from source control
- Add supporting source files for compatibility with external dependencies
Have questions or issues?
Swift Package Manager
Add Mockingbird as a package and test target dependency in your Package.swift
manifest.
let package = Package(
name: "MyPackage",
dependencies: [
.package(name: "Mockingbird", url: "https://github.com/birdrides/mockingbird.git", .upToNextMinor(from: "0.18.0")),
],
targets: [
.testTarget(name: "MyPackageTests", dependencies: ["Mockingbird"]),
]
)
In your project directory, initialize the package dependency.
Parsing the
DERIVED_DATA
path can take a minute.
$ xcodebuild -resolvePackageDependencies
$ DERIVED_DATA="$(xcodebuild -showBuildSettings | pcregrep -o1 'OBJROOT = (/.*)/Build')"
$ REPO_PATH="${DERIVED_DATA}/SourcePackages/checkouts/mockingbird"
Finally, configure a test target to generate mocks for each listed source module. This adds a build phase to the test target which calls mockingbird generate
. For advanced usages, modify the installed build phase or set up targets manually.
Not using an Xcode project? Generate mocks from the command line by calling
mockingbird generate
.
$ "${REPO_PATH}/mockingbird" install --target MyPackageTests --sources MyPackage MyLibrary1 MyLibrary2
Optional but recommended:
- Exclude generated files from source control
- Add supporting source files for compatibility with external dependencies
Have questions or issues?
Mockingbird provides a comprehensive API reference generated with SwiftDoc.
Mocks can be passed as instances of the original type, recording any calls they receive for later verification. Note that mocks are strict by default, meaning that calls to unstubbed non-void methods will trigger a test failure. To create a relaxed or “loose” mock, use a default value provider.
// Swift types
let protocolMock = mock(MyProtocol.self)
let classMock = mock(MyClass.self).initialize(…)
// Objective-C types
let protocolMock = mock(MyProtocol.self)
let classMock = mock(MyClass.self)
Swift class mocks rely on subclassing the original type which comes with a few known limitations. When creating a Swift class mock, you must initialize the instance by calling initialize(…)
with appropriate values.
class Tree {
init(height: Int) { assert(height > 0) }
}
let tree = mock(Tree.self).initialize(height: 42) // Initialized
let tree = mock(Tree.self).initialize(height: 0) // Assertion failed (height ≤ 0)
Generated Swift mock types are suffixed with Mock
. Avoid coercing mocks into their original type as stubbing and verification will no longer work.
// Good
let bird: BirdMock = mock(Bird.self) // Concrete type is `BirdMock`
let bird = mock(Bird.self) // Inferred type is `BirdMock`
// Avoid
let bird: Bird = mock(Bird.self) // Type is coerced into `Bird`
You can reset mocks and clear specific metadata during test runs. However, resetting mocks isn’t usually necessary in well-constructed tests.
reset(bird) // Reset everything
clearStubs(on: bird) // Only remove stubs
clearInvocations(on: bird) // Only remove recorded invocations
Stubbing allows you to define custom behavior for mocks to perform.
given(bird.name).willReturn("Ryan") // Return a value
given(bird.chirp()).willThrow(BirdError()) // Throw an error
given(bird.chirp(volume: any())).will { volume in // Call a closure
return volume < 42
}
This is equivalent to the shorthand syntax using the stubbing operator ~>
.
given(bird.name) ~> "Ryan" // Return a value
given(bird.chirp()) ~> { throw BirdError() } // Throw an error
given(bird.chirp(volume: any())) ~> { volume in // Call a closure
return volume < 42
}
Match argument values to stub parameterized methods. Stubs added later have a higher precedence, so add stubs with specific matchers last.
given(bird.chirp(volume: any())).willReturn(true) // Any volume
given(bird.chirp(volume: notNil())).willReturn(true) // Any non-nil volume
given(bird.chirp(volume: 10)).willReturn(true) // Volume = 10
Properties can have stubs on both their getters and setters.
given(bird.name).willReturn("Ryan")
given(bird.name = any()).will { (name: String) in
print("Hello \(name)")
}
print(bird.name) // Prints "Ryan"
bird.name = "Sterling" // Prints "Hello Sterling"
This is equivalent to using the synthesized getter and setter methods.
given(bird.getName()).willReturn("Ryan")
given(bird.setName(any())).will { (name: String) in
print("Hello \(name)")
}
print(bird.name) // Prints "Ryan"
bird.name = "Sterling" // Prints "Hello Sterling"
Readwrite properties can be stubbed to automatically save and return values.
given(bird.name).willReturn(lastSetValue(initial: ""))
print(bird.name) // Prints ""
bird.name = "Ryan"
print(bird.name) // Prints "Ryan"
Use a ValueProvider
to create a relaxed mock that returns default values for unstubbed methods. Mockingbird provides preset value providers which are guaranteed to be backwards compatible, such as .standardProvider
.
let bird = mock(Bird.self)
bird.useDefaultValues(from: .standardProvider)
print(bird.name) // Prints ""
You can create custom value providers by registering values for specific types.
var valueProvider = ValueProvider()
valueProvider.register("Ryan", for: String.self)
bird.useDefaultValues(from: valueProvider)
print(bird.name) // Prints "Ryan"
Values from concrete stubs always have a higher precedence than default values.
given(bird.name).willReturn("Ryan")
print(bird.name) // Prints "Ryan"
bird.useDefaultValues(from: .standardProvider)
print(bird.name) // Prints "Ryan"
Provide wildcard instances for generic types by conforming the base type to Providable
and registering the type.
extension Array: Providable {
public static func createInstance() -> Self? {
return Array()
}
}
// Provide an empty array for all specialized `Array` types
valueProvider.registerType(Array<Any>.self)
Partial mocks can be created by forwarding all calls to a specific object. Forwarding targets are strongly referenced and receive invocations until removed with clearStubs
.
class Crow: Bird {
let name: String
init(name: String) { self.name = name }
}
let bird = mock(Bird.self)
bird.forwardCalls(to: Crow(name: "Ryan"))
print(bird.name) // Prints "Ryan"
Swift class mocks can also forward invocations to its underlying superclass.
let tree = mock(Tree.self).initialize(height: 42)
tree.forwardCallsToSuper()
print(tree.height) // Prints "42"
For more granular stubbing, it’s possible to scope both object and superclass forwarding targets to a specific declaration.
given(bird.name).willForward(to: Crow(name: "Ryan")) // Object target
given(tree.height).willForwardToSuper() // Superclass target
Concrete stubs always have a higher priority than forwarding targets, regardless of the order they were added.
given(bird.name).willReturn("Ryan")
given(bird.name).willForward(to: Crow(name: "Sterling"))
print(bird.name) // Prints "Ryan"
Methods that return a different value each time can be stubbed with a sequence of values. The last value will be used for all subsequent invocations.
given(bird.name).willReturn(sequence(of: "Ryan", "Sterling"))
print(bird.name) // Prints "Ryan"
print(bird.name) // Prints "Sterling"
print(bird.name) // Prints "Sterling"
It’s also possible to stub a sequence of arbitrary behaviors.
given(bird.name)
.willReturn("Ryan")
.willReturn("Sterling")
.will { return Bool.random() ? "Ryan" : "Sterling" }
Verification lets you assert that a mock received a particular invocation during its lifetime.
verify(bird.fly()).wasCalled()
Verifying doesn’t remove recorded invocations, so it’s safe to call verify
multiple times.
verify(bird.fly()).wasCalled() // If this succeeds...
verify(bird.fly()).wasCalled() // ...this also succeeds
Match argument values to verify methods with parameters.
verify(bird.chirp(volume: any())).wasCalled() // Any volume
verify(bird.chirp(volume: notNil())).wasCalled() // Any non-nil volume
verify(bird.chirp(volume: 10)).wasCalled() // Volume = 10
Verify property invocations using their getter and setter methods.
verify(bird.name).wasCalled()
verify(bird.name = any()).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
An argument captor extracts received argument values which can be used in other parts of the test.
let bird = mock(Bird.self)
bird.name = "Ryan"
let nameCaptor = ArgumentCaptor<String>()
verify(bird.name = nameCaptor.any()).wasCalled()
print(nameCaptor.value) // Prints "Ryan"
Enforce the relative order of invocations with an inOrder
verification block.
// Verify that `canFly` was called before `fly`
inOrder {
verify(bird.canFly).wasCalled()
verify(bird.fly()).wasCalled()
}
Pass options to inOrder
verification blocks for stricter checks with additional invariants.
inOrder(with: .noInvocationsAfter) {
verify(bird.canFly).wasCalled()
verify(bird.fly()).wasCalled()
}
Mocked methods that are invoked asynchronously can be verified using an eventually
block which returns an XCTestExpectation
.
DispatchQueue.main.async {
guard bird.canFly else { return }
bird.fly()
}
let expectation =
eventually {
verify(bird.canFly).wasCalled()
verify(bird.fly()).wasCalled()
}
wait(for: [expectation], timeout: 1.0)
Use the returning
modifier to disambiguate methods overloaded by return type. Methods overloaded by parameter types do not require disambiguation.
protocol Bird {
func getMessage<T>() -> T // Overloaded generically
func getMessage() -> String // Overloaded explicitly
func getMessage() -> Data
}
verify(bird.getMessage()).returning(String.self).wasCalled()
Argument matching allows you to stub or verify specific invocations of parameterized methods.
Types that explicitly conform to Equatable
work out of the box, such as String
.
given(bird.chirp(volume: 42)).willReturn(true)
print(bird.chirp(volume: 42)) // Prints "true"
verify(bird.chirp(volume: 42)).wasCalled() // Passes
Structs able to synthesize Equatable
conformance must explicitly declare conformance to enable exact argument matching.
struct Fruit: Equatable {
let size: Int
}
bird.eat(Fruit(size: 42))
verify(bird.eat(Fruit(size: 42))).wasCalled()
Non-equatable classes are compared by reference instead.
class Fruit {}
let fruit = Fruit()
bird.eat(fruit)
verify(bird.eat(fruit)).wasCalled()
Argument matchers allow for wildcard or custom matching of arguments that are not Equatable
.
any() // Any value
any(of: 1, 2, 3) // Any value in {1, 2, 3}
any(where: { $0 > 42 }) // Any number greater than 42
notNil() // Any non-nil value
For methods overloaded by parameter type, you should 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) // Any collection with values {1, 2, 3}
any(keys: "a", "b", "c") // Any dictionary with keys {"a", "b", "c"}
any(count: atMost(42)) // Any collection with at most 42 elements
notEmpty() // Any non-empty collection
You must specify an argument position when matching an Objective-C method with multiple value type parameters. Mockingbird will raise a test failure if the argument position is not inferrable and no explicit position was provided.
@objc class Bird: NSObject {
@objc dynamic func chirp(volume: Int, duration: Int) {}
}
verify(bird.chirp(volume: firstArg(any()),
duration: secondArg(any())).wasCalled()
// Equivalent verbose syntax
verify(bird.chirp(volume: arg(any(), at: 1),
duration: arg(any(), at: 2)).wasCalled()
Mathematical operations on floating point numbers can cause loss of precision. Fuzzily match floating point arguments instead of using exact values to increase the robustness of tests.
around(10.0, tolerance: 0.01)
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.
Supporting source files are used by the generator to resolve inherited types defined outside of your project. Although Mockingbird provides a preset “starter pack” for basic compatibility with common system frameworks, you will occasionally need to add your own definitions for third-party library types. Please see Supporting Source Files for more information.
To improve compilation times for large projects, Mockingbird only generates mocking code (known as thunks) for types used in tests. Unused types can either produce “thunk stubs” or no code at all depending on the pruning level specified.
Level | Description |
---|---|
disable |
Always generate full thunks regardless of usage in tests. |
stub |
Generate partial definitions filled with fatalError . |
omit |
Don’t generate any definitions for unused types. |
Usage is determined by statically analyzing test target sources for calls to mock(SomeType.self)
, which may not work out of the box for projects that indirectly synthesize types such as through Objective-C based dependency injection.
- Option 1: Explicitly reference each indirectly synthesized type in your tests, e.g.
_ = mock(SomeType.self)
. References can be placed anywhere in the test target sources, such as in thesetUp
method of a test case or in a single file. - Option 2: Disable pruning entirely by setting the prune level with
--prune disable
. Note that this may increase compilation times for large projects.
Generate mocks for a set of targets in a project.
mockingbird generate
Option | Default Value | Description |
---|---|---|
--targets |
(required) | List of target names to generate mocks for. |
--project |
(inferred) |
Path to an .xcodeproj file or a JSON project description. |
--srcroot |
(inferred) |
The directory containing your project’s source files. |
--outputs |
(inferred) |
List of mock output file paths for each target. |
--support |
(inferred) |
The directory containing supporting source files. |
--testbundle |
(inferred) |
The name of the test bundle using the mocks. |
--header |
(none) |
Content to add at the beginning of each generated mock file. |
--condition |
(none) |
Compilation condition to wrap all generated mocks in, e.g. DEBUG . |
--diagnostics |
(none) |
List of diagnostic generator warnings to enable. |
--prune |
omit |
The pruning method to use on unreferenced types. |
Flag | Description |
---|---|
--only-protocols |
Only generate mocks for protocols. |
--disable-module-import |
Omit @testable import <module> from generated mocks. |
--disable-swiftlint |
Disable all SwiftLint rules in generated mocks. |
--disable-cache |
Ignore cached mock information stored on disk. |
--disable-relaxed-linking |
Only search explicitly imported modules. |
Configure a test target to use mocks.
mockingbird install
Option | Default Value | Description |
---|---|---|
--target |
(required) | The name of a test target to configure. |
--sources |
(required) | List of target names to generate mocks for. |
--project |
(inferred) |
Path to an .xcodeproj file or a JSON project description. |
--srcroot |
(inferred) |
The directory containing your project’s source files. |
--outputs |
(inferred) |
List of mock output file paths for each target. |
--support |
(inferred) |
The directory containing supporting source files. |
--header |
(none) |
Content to add at the beginning of each generated mock file. |
--condition |
(none) |
Compilation condition to wrap all generated mocks in, e.g. DEBUG . |
--diagnostics |
(none) |
List of diagnostic generator warnings to enable. |
--loglevel |
(none) |
The log level to use when generating mocks, quiet or verbose . |
--prune |
omit |
The pruning method to use on unreferenced types. |
Flag | Description |
---|---|
--preserve-existing |
Don’t overwrite previously installed configurations. |
--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. |
--disable-relaxed-linking |
Only search explicitly imported modules. |
Remove Mockingbird from a 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 |
(inferred) |
The directory containing your project’s source files. |
Download and unpack a compatible asset bundle. Bundles will never overwrite existing files on disk.
mockingbird download <asset>
Asset | Description |
---|---|
starter-pack |
Starter supporting source files. |
Option | Default Value | Description |
---|---|---|
--url |
https://github.com/birdrides/mockingbird/releases/download |
The base URL containing downloadable asset bundles. |
Flag | Description |
---|---|
--verbose |
Log all errors, warnings, and debug messages. |
--quiet |
Only log error messages. |
Mockingbird first checks the environment variable PROJECT_FILE_PATH
set by the Xcode build context and then performs 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.
Mockingbird checks the environment variables SRCROOT
and SOURCE_ROOT
set by the Xcode build context and then falls back to the directory containing the .xcodeproj
project file. Note that source root is ignored when using JSON project descriptions.
By Mockingbird generates mocks into the directory $(SRCROOT)/MockingbirdMocks
with the file name $(PRODUCT_MODULE_NAME)Mocks.generated.swift
.
Mockingbird recursively looks for supporting source files in the directory $(SRCROOT)/MockingbirdSupport
.
Mockingbird checks the environment variables TARGET_NAME
and TARGETNAME
set by the Xcode build context and verifies that it refers to a valid Swift unit test target. The test bundle option must be set when using JSON project descriptions in order to enable thunk stubs.