A fast mock-generation tool for Swift.
UltiMock can generate mocks based on protocols or open classes. It was greatly inspired by SwiftyMocky.
Like SwiftyMocky it uses Sourcery.
Instead of calling Sourcery's CLI it uses SourceryFramework
for parsing source code
and generates the mocks directly in Swift without any template languages.
This makes it ⚡️ fast and suitable for running as a Swift Package build plugin.
It also uses SourceKitten for generating Swift interfaces for Swift and Objective-C frameworks, which allows generating mocks for SDK protocols and classes.
Works with XCTest and Swift Testing.
- Generating mocks from command-line and Swift Package build plugin
- Supports mocking of:
- methods and properties
- generics and associated types
- async and throwing
open
classes- Swift Package dependencies
- Swift and Objective-C framework interfaces (you can mock
UIViewController
, hey!)
- Simple and concise interface: just
expect
andverify
- Resetting expectations
- Auto-forwarding for class mocks
- Respects expectations order
- Annotate the protocols or classes you need to mock:
// sourcery:AutoMockable
extension MyProtocol {}
// sourcery:AutoMockable
extension MyClass {}
-
Create mock.json configuration file in your test target directory. The
sources
parameter should contain a directory with the annotated interfaces. -
Add
UltiMock
package plugin to yourPackage.swift
:
dependencies: [
.package(name: "UltiMock", url: "https://github.com/smg-real-estate/Swift-UltiMock", from: "0.6.2"),
],
targets: [
.testTarget(
name: "MyAppTests",
plugins: [
.plugin(name: "MockGenerationPlugin", package: "UltiMock")
]
)
]
To reduce compiling times and avoid conflicts with macro packages you can use a binary version instead:
dependencies: [
.package(name: "UltiMock", url: "https://github.com/smg-real-estate/Swift-UltiMock-Binary", from: "0.6.2"),
],
- Use the generated mocks in your tests:
let storageMock = TextStorageMock()
// ---
storageMock.expect(.loadText(from: .value("filename"))) { "Lorem ipsum" // Stub return value in perform closure }
XCTAssertEqual(sut.text, "Lorem ipsum")
// This will fail if any expectations left unfulfilled
storageMock.verify()
For Equatable
parameters you can use value()
constructor:
mock.expect(.loadText(from: .value("filename"))) { "Lorem ipsum" // Stub return value in perform closure }
The mock will automatically check if the passed value is equal to the expected one.
For many Equatable
types expressible by a literal you can use the literal value as an expectation parameter directly:
mock.expect(.loadText(from: "filename")) { "Lorem ipsum" }
mock.expect(.saveItems(["A", "B", "C"]))
mock.expect(.saveItems(["A" : 1, "B" : 2]))
For checking the identity of a reference type you may use identical(to:)
parameter constructor:
// Making sure we're presenting the same view controller instance
mock.expect(.present(.identical(to: viewController)))
For checking if an optional value is nil
or non-nil
you may use a nil
literal, isNil
or isNotNil
parameter constructors:
mock.expect(.present(.identical(to: viewController, completion: nil)))
// or
mock.expect(.present(.identical(to: viewController, completion: .isNil)))
mock.expect(.present(.identical(to: viewController, completion: .isNotNil))) { _, completion in
completion()
// Verifying the completion behavior
}
For non-Equatable
types or in some other cases you may use matching()
parameter constructor:
// Matching a tuple value
mock.expect(.doSomething(withTuple: .matching { value /*(String, String)*/ in
value.0 == "A" && value.1 == "B"
}))
When it's not possible to reliably match a parameter, such as when the parameter is a closure,
or when the parameter does not matter, you can use a wild-card any
parameter constructor:
mock.expect(.requestPermission(completion: .any))
When the parameter is generic you can explicitly specify its type:
mock.expect(.encode(.any(String.self)))
UltiMock supports both getter and setter property expectations.
A getter expectation is similar to a method expectation without parentheses:
mock.expect(.count) { 3 }
A setter expectation has a dedicated constructor:
mock.expect(set: .count, to: 3)
Just like the properties, UltiMock supports both getter and setter subscript expectations.
A getter expectation is similar to a method expectation but uses a subscript
accessor property
and an actual subscript syntax:
mock.expect(.subscript[3]) { _ in "3" }
// same as:
mock.expect(.subscript[.value(3)]) { _ in "3" }
A setter expectation has a dedicated constructor:
mock.expect(set: .subscript[3], to: "3")
// same as:
mock.expect(set: .subscript[.value(3)], to: .value("3")) { key, newValue in }
You may use a perform closure for stubbing a return value or for other needs:
mock.expect(.present(.identical(to: viewController, completion: .isNotNil))) { _, completion in
completion()
// Verifying the completion behavior
}
Note: The perform closure is optional for methods without a return value.
You can reset mock's expectations by calling resetExpectations()
method.
A class mock expectation uses same syntax as in protocol mocks except the extra perform closure parameter for calling the mocked method's original implementation:
mock.expect(set: .count, to: 3) { forwardToOriginal, newValue in
forwardToOriginal(newValue)
}
A class mock can be switched to the mocked class original behavior by setting autoForwardingEnabled
property to true
.
This can be used to avoid mocking precondition calls in complex test setups.