A powerful Swift CLI tool that automatically generates comprehensive mock objects (stubs, spies, and dummies) from your Swift source code using simple comment annotations. Perfect for unit testing, TDD, and creating reliable test doubles.
- π― Comment-based Generation: Generate mocks using simple annotations like
// @Stub,// @Spy,// @Dummy - π Zero Runtime Dependencies: Generated mocks have no dependencies on the generator tool
- π§ Multiple Mock Types: Supports stubs, spies, and dummy implementations
- β‘ Swift Syntax Powered: Uses Apple's SwiftSyntax for accurate Swift code parsing
- π οΈ CLI Interface: Easy to integrate into build processes and CI/CD pipelines
- π Call Tracking: Spies automatically track method calls, parameters, and return values
- π Error Mocking: Configure spies to throw specific errors for testing error scenarios
- π Clean Code: Generated mocks follow Swift best practices with organized structure
- π Verbose Logging: Detailed output for debugging and monitoring
- π Async Result Support: Generate mocks with
Result<T, Error>for async methods using--use-result - π Sendable Support: Automatically detects and marks mocks as
@unchecked Sendablewhen needed
# Clone the repository
git clone https://github.com/manucodin/SwiftMockGenerator.git
cd SwiftMockGenerator
# Install system-wide
make install
# Or run directly
swift run swift-mock-generator --input ./Sources --output ./Tests/Mocksπ± Mint
mint install manucodin/SwiftMockGenerator- Annotate your code with mock comments:
// @Stub
protocol NetworkService {
func fetchData() async throws -> Data
var isConnected: Bool { get }
}
// @Spy
class DataManager {
func save(data: String) throws -> Bool {
return true
}
}- Generate mocks:
# Standard generation
swift-mock-generator --input ./Sources --output ./Tests/Mocks --verbose
# With Result<T, Error> for async methods
swift-mock-generator --input ./Sources --output ./Tests/Mocks --use-result --verbose- Use in your tests:
func testDataManager() throws {
let spy = DataManagerSpy()
spy.saveReturnValue = true
spy.saveThrowError = NetworkError.connectionFailed
// Test your code with the spy
XCTAssertThrowsError(try spy.save(data: "test"))
XCTAssertEqual(spy.saveCallCount, 1)
}| Option | Short | Description | Default |
|---|---|---|---|
--input |
-i |
Input directory containing Swift source files | . |
--output |
-o |
Output directory for generated mock files | ./Mocks |
--verbose |
-v |
Enable verbose logging | false |
--clean |
Clean output directory before generating mocks | false |
|
--use-result |
Use Result<T, Error> for async methods instead of async throws |
false |
When working with async methods, you can choose between two approaches:
// @Stub
protocol NetworkService {
func fetchData() async throws -> Data
}Generated Mock:
class NetworkServiceStub: NetworkService {
func fetchData() async throws -> Data {
return Data()
}
}swift-mock-generator --input ./Sources --output ./Tests/Mocks --use-resultGenerated Mock:
class NetworkServiceStub: NetworkService {
var fetchDataReturnValue: Result<Data, Error> = .success(Data())
func fetchData() async throws -> Data {
return try fetchDataReturnValue.get()
}
}Usage in Tests:
func testNetworkService() async throws {
let stub = NetworkServiceStub()
// Simulate success
stub.fetchDataReturnValue = .success(sampleData)
// Simulate error
stub.fetchDataReturnValue = .failure(NetworkError.timeout)
let result = try await stub.fetchData()
// Handle the unwrapped Data value
}The tool automatically detects when protocols or classes conform to Sendable and marks the generated mocks accordingly:
// @Stub
protocol SendableService: Sendable {
func fetchData() async throws -> String
}Generated Mock:
@unchecked Sendable class SendableServiceStub: SendableService, Sendable {
var fetchDataReturnValue: Result<String, Error> = .success("")
func fetchData() async throws -> String {
return try fetchDataReturnValue.get()
}
}This ensures your mocks are safe to use in concurrent contexts when the original type is Sendable.
Generates implementations with sensible default return values:
// @Stub
protocol UserService {
func getUser(id: String) async throws -> User
var isLoggedIn: Bool { get }
}Generated Stub:
class UserServiceStub: UserService {
var getUserReturnValue: User = User()
var isLoggedInReturnValue: Bool = false
func getUser(id: String) async throws -> User {
return getUserReturnValue
}
var isLoggedIn: Bool {
return isLoggedInReturnValue
}
}Generates implementations that record method calls and parameters:
// @Spy
class DataRepository {
func save(_ item: Item) throws -> Bool {
return true
}
func load(id: String) -> Item? {
return nil
}
}Generated Spy:
class DataRepositorySpy: DataRepository {
// MARK: - Reset
func resetSpy() {
saveCallCount = 0
saveCallParameters = []
saveThrowError = nil
loadCallCount = 0
loadCallParameters = []
loadReturnValue = nil
}
// MARK: - save
private(set) var saveCallCount = 0
private(set) var saveCallParameters: [(Item)] = []
var saveThrowError: Error?
var saveReturnValue: Bool = false
func save(_ item: Item) throws -> Bool {
saveCallCount += 1
saveCallParameters.append((item))
if let error = saveThrowError { throw error }
return saveReturnValue
}
// MARK: - load
private(set) var loadCallCount = 0
private(set) var loadCallParameters: [(String)] = []
var loadReturnValue: Item?
func load(id: String) -> Item? {
loadCallCount += 1
loadCallParameters.append((id))
return loadReturnValue
}
}Generates minimal implementations that satisfy compile-time requirements:
// @Dummy
protocol Logger {
func log(message: String)
func logError(_ error: Error)
}Generated Dummy:
class LoggerDummy: Logger {
func log(message: String) {
// Dummy implementation - does nothing
}
func logError(_ error: Error) {
// Dummy implementation - does nothing
}
}make help # Show all available commands
make install # Build and install system-wide
make uninstall # Remove from system
make test # Run test suite (99 tests)
make coverage # Run tests with coverage report
make demo # See the tool in action with examples
make clean # Clean build artifacts- β Protocols with methods, properties, and inheritance
- β Classes with inheritance and final modifiers
- β Functions with parameters, return types, and async/await
- β Generic types and functions
- β Throwing functions with error mocking
- β Access control levels (public, internal, private, fileprivate)
- β Property wrappers and computed properties
- β Initializers with various modifiers
- β
Sendable conformance with automatic
@unchecked Sendablemarking - β
Async/await with optional
Result<T, Error>support
The project can be opened and executed directly from Xcode for development and debugging purposes.
- Open Xcode
- File β Open
- Select the
Package.swiftfile in the project root - Click "Open"
To run the project with custom arguments:
- Product β Scheme β Edit Scheme...
- In the "Run" tab:
- Ensure the Executable is set to
SwiftMockGenerator
- Ensure the Executable is set to
- In the "Arguments" tab:
- Add command line arguments as needed:
--input "./Examples/Sources"(input directory)--output "./Examples/Mocks"(output directory)--verbose(enable detailed logging)--clean(clean output directory before generation)--module "MyModule"(specify module name for @testable import)--use-result(use Result<T, Error> for async methods)
- Add command line arguments as needed:
- Press Cmd+R to build and run
- Or use Product β Run from the menu
- Check the console output for generation results
Add a build phase script to automatically generate mocks:
if which swift-mock-generator > /dev/null; then
swift-mock-generator --input ./Sources --output ./Tests/Mocks --verbose
else
echo "SwiftMockGenerator not found. Installing..."
# Add installation commands here
exit 1
fiAdd as a dependency in your Package.swift:
dependencies: [
.package(url: "https://github.com/manucodin/SwiftMockGenerator.git", from: "1.0.0")
]- Compilation Errors: Ensure your input Swift files are syntactically correct
- No Mocks Generated: Check that comment annotations are properly formatted
- Access Level Issues: Generated mocks respect the access levels of original types
- Missing Dependencies: Ensure Swift 5.9+ and macOS 12+ are installed
- Sendable Warnings: Use
@unchecked Sendablefor mocks that need to be Sendable but have mutable state - Async Result Issues: Use
--use-resultflag when you needResult<T, Error>instead ofasync throws
Use --verbose flag to see detailed logging:
swift-mock-generator --input ./Sources --output ./Tests/Mocks --verbose- Check the Examples directory for sample usage
- Run
make demoto see the tool in action - Review the test suite for implementation patterns
- Swift: 5.9+
- macOS: 12+
- Xcode: 14+ (for development)
- Swift Argument Parser - CLI interface
- SwiftSyntax - Swift code parsing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Add tests for new functionality
- Ensure all tests pass (
make test) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Built with SwiftSyntax by Apple
- CLI powered by Swift Argument Parser
- Inspired by modern testing practices and TDD methodologies
Made with β€οΈ for the Swift community