{"id":16568530,"url":"https://github.com/matejkob/swift-spyable","last_synced_at":"2025-05-15T21:06:50.290Z","repository":{"id":175345835,"uuid":"651621098","full_name":"Matejkob/swift-spyable","owner":"Matejkob","description":"Swift macro that simplifies and automates the process of creating spies for testing","archived":false,"fork":false,"pushed_at":"2024-12-24T13:33:09.000Z","size":414,"stargazers_count":442,"open_issues_count":11,"forks_count":43,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-05-14T10:59:47.747Z","etag":null,"topics":["software-quality","swift","swift-macros","testing","unit-testing"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Matejkob.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-06-09T16:48:20.000Z","updated_at":"2025-05-14T10:56:54.000Z","dependencies_parsed_at":null,"dependency_job_id":"6350333b-c4db-4b81-9d1b-87abb8dbbba8","html_url":"https://github.com/Matejkob/swift-spyable","commit_stats":{"total_commits":200,"total_committers":11,"mean_commits":"18.181818181818183","dds":0.25,"last_synced_commit":"0328b9ce800999300d1c4601fd34519ea6a93878"},"previous_names":["matejkob/swift-spyable"],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Matejkob%2Fswift-spyable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Matejkob%2Fswift-spyable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Matejkob%2Fswift-spyable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Matejkob%2Fswift-spyable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Matejkob","download_url":"https://codeload.github.com/Matejkob/swift-spyable/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254422761,"owners_count":22068678,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["software-quality","swift","swift-macros","testing","unit-testing"],"created_at":"2024-10-11T21:10:35.476Z","updated_at":"2025-05-15T21:06:50.265Z","avatar_url":"https://github.com/Matejkob.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Spyable\n\n[![GitHub Workflow Status](https://github.com/Matejkob/swift-spyable/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/Matejkob/swift-spyable/actions/workflows/ci.yml)\n[![codecov](https://codecov.io/gh/Matejkob/swift-spyable/graph/badge.svg?token=YRMM1BDQ85)](https://codecov.io/gh/Matejkob/swift-spyable)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FMatejkob%2Fswift-spyable%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/Matejkob/swift-spyable)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FMatejkob%2Fswift-spyable%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/Matejkob/swift-spyable)\n\nSpyable is a powerful tool for Swift that automates the process of creating protocol-conforming classes. Initially designed to simplify testing by generating spies, it is now widely used for various scenarios, such as SwiftUI previews or creating quick dummy implementations.\n\n## Overview\n\nSpyable enhances your Swift workflow with the following features:\n\n- **Automatic Spy Generation**: Annotate a protocol with `@Spyable`, and let the macro generate a corresponding spy class.\n- **Access Level Inheritance**: The generated class automatically inherits the protocol's access level.\n- **Explicit Access Control**: Use the `accessLevel` argument to override the inherited access level if needed.\n- **Interaction Tracking**: For testing, the generated spy tracks method calls, arguments, and return values.\n\n## Quick Start\n\n1. Import Spyable: `import Spyable`\n2. Annotate your protocol with `@Spyable`:\n\n```swift\n@Spyable\npublic protocol ServiceProtocol {\n  var name: String { get }\n  func fetchConfig(arg: UInt8) async throws -\u003e [String: String]\n}\n```\n\nThis generates a spy class named `ServiceProtocolSpy` with a `public` access level. The generated class includes properties and methods for tracking method calls, arguments, and return values.\n\n```swift\npublic class ServiceProtocolSpy: ServiceProtocol {\n  public var name: String {\n    get { underlyingName }\n    set { underlyingName = newValue }\n  }\n  public var underlyingName: (String)!\n\n  public var fetchConfigArgCallsCount = 0\n  public var fetchConfigArgCalled: Bool {\n    return fetchConfigArgCallsCount \u003e 0\n  }\n  public var fetchConfigArgReceivedArg: UInt8?\n  public var fetchConfigArgReceivedInvocations: [UInt8] = []\n  public var fetchConfigArgThrowableError: (any Error)?\n  public var fetchConfigArgReturnValue: [String: String]!\n  public var fetchConfigArgClosure: ((UInt8) async throws -\u003e [String: String])?\n\n  public func fetchConfig(arg: UInt8) async throws -\u003e [String: String] {\n    fetchConfigArgCallsCount += 1\n    fetchConfigArgReceivedArg = (arg)\n    fetchConfigArgReceivedInvocations.append((arg))\n    if let fetchConfigArgThrowableError {\n      throw fetchConfigArgThrowableError\n    }\n    if fetchConfigArgClosure != nil {\n      return try await fetchConfigArgClosure!(arg)\n    } else {\n      return fetchConfigArgReturnValue\n    }\n  }\n}\n```\n\n3. Use the spy in your tests:\n\n```swift\nfunc testFetchConfig() async throws {\n  let serviceSpy = ServiceProtocolSpy()\n  let sut = ViewModel(service: serviceSpy)\n\n  serviceSpy.fetchConfigArgReturnValue = [\"key\": \"value\"]\n\n  try await sut.fetchConfig()\n\n  XCTAssertEqual(serviceSpy.fetchConfigArgCallsCount, 1)\n  XCTAssertEqual(serviceSpy.fetchConfigArgReceivedInvocations, [1])\n\n  try await sut.saveConfig()\n\n  XCTAssertEqual(serviceSpy.fetchConfigArgCallsCount, 2)\n  XCTAssertEqual(serviceSpy.fetchConfigArgReceivedInvocations, [1, 1])\n}\n```\n\n## Advanced Usage\n\n### Access Level Inheritance and Overrides\n\nBy default, the generated spy inherits the access level of the annotated protocol. For example:\n\n```swift\n@Spyable\ninternal protocol InternalProtocol {\n  func doSomething()\n}\n```\n\nThis generates:\n\n```swift\ninternal class InternalProtocolSpy: InternalProtocol {\n  internal func doSomething() { ... }\n}\n```\n\nYou can override this behavior by explicitly specifying an access level:\n\n```swift\n@Spyable(accessLevel: .fileprivate)\npublic protocol CustomProtocol {\n  func restrictedTask()\n}\n```\n\nGenerates:\n\n```swift\nfileprivate class CustomProtocolSpy: CustomProtocol {\n  fileprivate func restrictedTask() { ... }\n}\n```\n\nSupported values for `accessLevel` are:\n- `.public`\n- `.package`\n- `.internal`\n- `.fileprivate`\n- `.private`\n\n### Restricting Spy Availability\n\nUse the `behindPreprocessorFlag` parameter to wrap the generated code in a preprocessor directive:\n\n```swift\n@Spyable(behindPreprocessorFlag: \"DEBUG\")\nprotocol DebugProtocol {\n  func logSomething()\n}\n```\n\nGenerates:\n\n```swift\n#if DEBUG\ninternal class DebugProtocolSpy: DebugProtocol {\n  internal func logSomething() { ... }\n}\n#endif\n```\n\n## Installation\n\n### Xcode Projects\n\nAdd Spyable as a package dependency:\n\n```\nhttps://github.com/Matejkob/swift-spyable\n```\n\n### Swift Package Manager\n\nAdd to your `Package.swift`:\n\n```swift\ndependencies: [\n  .package(url: \"https://github.com/Matejkob/swift-spyable\", from: \"0.3.0\")\n]\n```\n\nThen, add the product to your target:\n\n```swift\n.product(name: \"Spyable\", package: \"swift-spyable\"),\n```\n\n## License\n\nThis library is released under the MIT license. See [LICENSE](LICENSE) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmatejkob%2Fswift-spyable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmatejkob%2Fswift-spyable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmatejkob%2Fswift-spyable/lists"}