Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
0xLeif committed Nov 2, 2024
1 parent 0c80ccc commit 2a26499
Show file tree
Hide file tree
Showing 7 changed files with 611 additions and 48 deletions.
74 changes: 74 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Mocked

Mocked is a Swift compiler macro that automatically generates mock implementations for protocols. This can be especially useful for unit testing, allowing you to easily create mock objects to verify behavior and interactions in your tests.

## Features

- **Automatic Mock Generation**: Simply annotate your protocol with `@Mocked`, and a mock implementation will be generated.
- **Supports Properties and Methods**: Generates mock versions of properties and methods, including `async` and `throws` variants.
- **Configurable Behavior**: Easily override behavior by providing closures during initialization of the mock.

## Installation

To use Mocked in your project, add it as a dependency using Swift Package Manager. Add the following to your `Package.swift` file:

```swift
.package(url: "https://github.com/0xLeif/Mocked.git", from: "1.0.0")
```

And add it as a dependency to your target:

```swift
.target(
name: "YourTargetName",
dependencies: [
"Mocked"
]
)
```

## Usage

To generate a mock for a protocol, simply annotate it with `@Mocked`:

```swift
@Mocked
protocol MyProtocol {
var title: String { get set }
func performAction() -> Void
}
```

This will generate a mock struct named `MockedMyProtocol` that conforms to `MyProtocol`. You can use this mock in your unit tests to validate behavior.

### Example

```swift
@Mocked
protocol MyProtocol {
var title: String { get set }
func performAction() -> Void
}

let mock = MockedMyProtocol(
title: "Test Title",
performAction: { print("Action performed") }
)

mock.performAction() // Output: "Action performed"
```

### Edge Cases and Warnings

- **Non-Protocol Usage**: The `@Mocked` macro can only be applied to protocols. Using it on other types will result in a compilation error.
- **Unimplemented Methods**: Any method that is not overridden will call `fatalError()` if invoked. Ensure all required methods are implemented when using the generated mock.
- **Async and Throwing Methods**: The generated mocks handle `async` and `throws` methods appropriately, but be sure to provide closures that match the method signatures.

## Contributing

Contributions are welcome! If you have suggestions, issues, or improvements, feel free to open a pull request or issue on the [GitHub repository](https://github.com/0xLeif/Mocked).

## License

This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more information.

55 changes: 46 additions & 9 deletions Sources/Mocked/Mocked.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,48 @@
// The Swift Programming Language
// https://docs.swift.org/swift-book

/// A macro that produces both a value and a string containing the
/// source code that generated the value. For example,
/// The `Mocked` macro is used to automatically generate a mocked implementation of a protocol.
///
/// #stringify(x + y)
/// This macro attaches a peer struct prefixed with `Mocked` that provides implementations of all the methods and properties defined in the protocol.
///
/// produces a tuple `(x + y, "x + y")`.
@freestanding(expression)
public macro stringify<T>(_ value: T) -> (T, String) = #externalMacro(module: "MockedMacros", type: "StringifyMacro")
/// # Usage
/// Apply the `@Mocked` attribute to a protocol declaration to generate a mock implementation of that protocol. This mock implementation can be used for unit testing purposes to easily verify interactions with the protocol methods and properties.
///
/// Example:
/// ```swift
/// @Mocked
/// protocol MyProtocol {
/// var title: String { get set }
/// func performAction() -> Void
/// }
/// ```
///
/// The code above will generate a `MockedMyProtocol` struct that implements `MyProtocol`.
///
/// # Edge Cases and Warnings
/// - **Non-Protocol Usage**: This macro can only be applied to protocol definitions. Attempting to use it on other types, such as classes or structs, will lead to a compilation error.
/// - **Unimplemented Methods**: Any method that is not explicitly overridden will call `fatalError()` when invoked, which will crash the program. Ensure all necessary methods are mocked when using the generated struct.
/// - **Async and Throwing Methods**: The macro correctly handles protocols with `async` and/or `throws` functions. Be mindful to provide appropriate closures during initialization.
///
/// # Example of Generated Code
/// For the protocol `MyProtocol`, the generated mock implementation would look like this:
/// ```swift
/// struct MockedMyProtocol: MyProtocol {
/// var title: String
/// private let performActionOverride: (() -> Void)?
///
/// init(title: String, performAction: (() -> Void)? = nil) {
/// self.title = title
/// self.performActionOverride = performAction
/// }
///
/// func performAction() {
/// guard let performActionOverride else {
/// fatalError("Mocked performAction was not implemented!")
/// }
/// performActionOverride()
/// }
/// }
/// ```
@attached(peer, names: prefixed(Mocked))
public macro Mocked() = #externalMacro(
module: "MockedMacros",
type: "MockedMacro"
)
37 changes: 33 additions & 4 deletions Sources/MockedClient/main.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,37 @@
import Mocked

let a = 17
let b = 25
protocol ThisBreaksShit {
var broken: String { get }
}

let (result, code) = #stringify(a + b)
@Mocked
protocol SomeParameter: Sendable {
var title: String { get set }
var description: String { get }

print("The value \(result) was produced by the code \"\(code)\"")
func someMethod()
func someMethod(parameter: Int)
func someMethod(with parameter: Int)

func someOtherMethod() throws -> String
func someOtherMethod() async throws -> String

func someAsyncMethod() async -> String

func someOptionalMethod() -> String?
}

Task { @MainActor in
let mockedParameter = MockedSomeParameter(
title: "Hello",
description: "Descrip",
someMethodParameter: { print("\($0)") },
someOtherMethodAsyncThrows: { "?" }
)

mockedParameter.someMethod(parameter: 3)
let value = try await mockedParameter.someOtherMethod()

print(value)

}
Loading

0 comments on commit 2a26499

Please sign in to comment.