https://github.com/yysskk/swift-mockable
A Swift Macro that generates mock classes from protocols for testing.
https://github.com/yysskk/swift-mockable
mock swift testing
Last synced: 3 months ago
JSON representation
A Swift Macro that generates mock classes from protocols for testing.
- Host: GitHub
- URL: https://github.com/yysskk/swift-mockable
- Owner: yysskk
- License: mit
- Created: 2026-01-19T10:04:50.000Z (5 months ago)
- Default Branch: main
- Last Pushed: 2026-01-25T01:55:52.000Z (5 months ago)
- Last Synced: 2026-01-25T21:49:41.266Z (5 months ago)
- Topics: mock, swift, testing
- Language: Swift
- Homepage:
- Size: 52.7 KB
- Stars: 3
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# swift-mockable
`swift-mockable` provides a `@Mockable` macro that generates protocol mocks for tests.
- Generated mocks are emitted inside `#if DEBUG`.
- Generated names follow a predictable convention (`CallCount`, `CallArgs`, `Handler`).
- `resetMock()` is generated to clear all tracking state.
## Installation
Add the package:
```swift
dependencies: [
.package(url: "https://github.com/yysskk/swift-mockable.git", from: "0.1.0")
]
```
Add `Mockable` to your target:
```swift
.target(
name: "YourTarget",
dependencies: ["Mockable"]
)
```
## Quick Start
```swift
import Mockable
@Mockable
protocol UserService {
func fetchUser(id: Int) async throws -> User
func saveUser(_ user: User) async throws
var currentUser: User? { get }
var isLoggedIn: Bool { get set }
}
let mock = UserServiceMock()
mock.fetchUserHandler = { id in
User(id: id, name: "Test User")
}
mock._currentUser = User(id: 1, name: "Current")
mock.isLoggedIn = true
let user = try await mock.fetchUser(id: 42)
#expect(user.id == 42)
#expect(mock.fetchUserCallCount == 1)
#expect(mock.fetchUserCallArgs == [42])
mock.resetMock()
#expect(mock.fetchUserCallCount == 0)
```
## What Gets Generated
For each protocol requirement, `@Mockable` generates test-friendly members:
- Functions:
- `CallCount`
- `CallArgs`
- `Handler`
- Properties:
- Backing storage for setup (for example `_`)
- Computed protocol-conforming property (`property`)
- Subscripts:
- `subscriptCallCount`
- `subscriptCallArgs`
- `subscriptHandler`
- `subscriptSetHandler` for get/set subscripts
- Utility:
- `resetMock()`
## Supported Features
- Access-level-aware generation (including `private` / `fileprivate` edge cases)
- Sync / `async` / `throws` methods
- Variadic parameters (captured as arrays)
- `inout` parameters with write-back support
- Generic methods (generic parameters are type-erased to `Any` in storage/handlers)
- Overloaded methods (unique suffixes are added to generated names when needed)
- Associated types (generated as `typealias`, using default type when available, otherwise `Any`)
- Static methods and static properties
- Get-only / get-set / optional properties
- Get-only / get-set subscripts
- `#if` / `#elseif` / `#else` conditional compilation inside protocols
- Protocol inheritance (child mock inherits from first parent mock when applicable)
- `Sendable` protocol support (`@unchecked Sendable` mock generation)
- `Actor` protocol support (actor mock generation with nonisolated helper members)
## Behavioral Notes
- Return-value methods and get-only subscripts `fatalError` if their handler is not set.
- Void-return methods and subscript setters are no-op when handler is `nil`.
- `resetMock()` clears handlers, call counts, call arguments, and backing properties.
- For inherited protocols, `resetMock()` calls `super.resetMock()` before resetting child members.
## Diagnostics and Limitations
- `@Mockable` can only be applied to protocols.
- `@Mockable` does not accept arguments.
- Unsupported protocol members (for example `init`) emit compile-time diagnostics.
- Static/class subscripts are not supported.
- For protocols with multiple parent protocols, the first parent is used as the mock superclass.
## Documentation
- [Docs index](docs/README.md)
- [Advanced usage and naming rules](docs/advanced-usage.md)
## Requirements
- Swift 5.9, 5.10, and 6.2+
- macOS 10.15+ / iOS 13+ / tvOS 13+ / watchOS 6+
- `MockableLock` lock strategy:
- iOS 18.0+ / macOS 15.0+ / tvOS 18.0+ / watchOS 11.0+: prefers `Mutex` (`Synchronization`)
- Older OS versions: falls back to `LegacyLock` (`NSLock`-based)
## License
MIT