A Swift command-line tool that automatically generates mock implementations for protocols marked with /// @mockable comment. SwiftMock simplifies unit testing by eliminating the need to manually write mock objects, allowing you to focus on writing tests instead of boilerplate code.
Writing mock implementations for protocols is repetitive and time-consuming. SwiftMock automates this process by:
- Automatically generating mock classes from your protocol definitions
- Tracking method calls with call counts and received arguments
- Supporting custom handlers for flexible test scenarios
- Handling complex method signatures including throws, return types, and multiple parameters
Add SwiftMock to your Package.swift:
dependencies: [
.package(url: "https://github.com/yourusername/SwiftMock.git", from: "1.0.0")
]git clone https://github.com/yourusername/SwiftMock.git
cd SwiftMock
swift build -c release
cp .build/release/swiftmock /usr/local/bin/Add the /// @mockable documentation comment above protocols you want to mock:
/// @mockable
protocol UserService {
func fetchUser(id: String) throws -> User
func deleteUser(id: String) async throws
func updateUserName(_ name: String, for id: String)
}Run the SwiftMock command:
swiftmock --source ./Sources --output ./Tests/Mocks/GeneratedMocks.swiftfunc testUserFetch() {
let mockService = UserServiceMock()
// Set up mock behavior
mockService.fetchUserHandler = { id in
return User(id: id, name: "Test User")
}
// Execute test
let user = try mockService.fetchUser(id: "123")
// Verify
XCTAssertEqual(mockService.fetchUserCallCount, 1)
XCTAssertEqual(mockService.fetchUserReceivedArguments.first, "123")
XCTAssertEqual(user.name, "Test User")
}| Flag | Short | Description |
|---|---|---|
--source |
-s |
Required. Root directory to scan for Swift files |
--output |
-o |
Required. Output file path for generated mocks |
--exclude |
-e |
Comma-separated list of folders to exclude (e.g., Build,Archive) |
--verbose |
-v |
Enable verbose output for debugging |
--per-file |
Generate a separate mock file for each protocol | |
--force |
Force generation on every run (skips incremental build check) |
The following examples demonstrate various ways to use SwiftMock depending on your project structure and requirements:
Basic usage:
swiftmock -s ./Sources -o ./Tests/Mocks/Mocks.swiftExclude specific folders:
swiftmock -s ./Sources -o ./Tests/Mocks/Mocks.swift -e "ThirdParty,Generated"Generate separate files per protocol:
swiftmock -s ./Sources -o ./Tests/Mocks/ --per-fileVerbose output with forced generation:
swiftmock -s ./Sources -o ./Tests/Mocks/Mocks.swift -v --forcepublic final class UserServiceMock: UserService {
public init() {}
var fetchUserCallCount = 0
var fetchUserHandler: ((String) throws -> User)?
var fetchUserReceivedArguments: [String] = []
func fetchUser(id: String) throws -> User {
fetchUserCallCount += 1
fetchUserReceivedArguments.append(id)
guard let handler = fetchUserHandler else {
fatalError("fetchUserHandler must be set before calling fetchUser")
}
return try handler(id)
}
}-
/// @mockable Comment: Only protocols marked with
/// @mockabledocumentation comment will be processed. Ensure you add this comment directly above the protocol declaration. -
Incremental Generation: By default, SwiftMock uses incremental builds to skip generation if source files haven't changed. Use
--forceto override this behavior. -
Handler Requirements: For methods with return types, you must set the handler before calling the method, or it will trigger a fatal error. This ensures tests explicitly define expected behavior.
-
File Organization: When using
--per-file, ensure the output path is a directory. SwiftMock will create separate files named{ProtocolName}Mock.swift. -
Excluded Paths: Use the
--excludeflag to skip folders like build outputs, third-party dependencies, or generated code directories. -
Swift Version: Requires Swift 6.1 or later and macOS 13+.
-
Access Control: Generated mocks are marked
publicto ensure they can be used across module boundaries in your tests.
Add a build phase script to automatically regenerate mocks:
- In Xcode, select your test target
- Go to Build Phases → New Run Script Phase
- Add the following script:
if which swiftmock >/dev/null; then
swiftmock -s "$SRCROOT/Sources" -o "$SRCROOT/Tests/Mocks/GeneratedMocks.swift"
else
echo "warning: SwiftMock not installed"
fiSwiftMock is released under the MIT License. See LICENSE file for details.
Made with ❤️ for the Swift testing community
