Skip to content

Commit 28d715d

Browse files
authored
Initial Commit
0 parents  commit 28d715d

File tree

83 files changed

+12023
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+12023
-0
lines changed
35.5 KB
Loading

CHANGELOG.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Version History
2+
3+
# 3.0.0
4+
5+
## Added
6+
7+
- `@FunctionBodyMock` macro for automatic method call proxying to the internal `Mock()` class in `@AnyMockable`.
8+
This is a helper macro and shouldn't be called manually.
9+
10+
- `@Nilable` macro for generating nil default values in @Arbitrary macro.
11+
Can only be applied to optional and force-unwrapped properties.
12+
13+
- `.Arbitrary()` method implementation for `Int64`.
14+
15+
## Technical changes
16+
17+
- Bumped Swift version requirement to 6.0.
18+
- Minimum swift-syntax version increased to 600.0.0.
19+
- Added strict-concurrency support for `@Mock` and `@AnyMockable`.
20+
21+
# 2.1.0
22+
23+
## Added
24+
25+
- Ability to add `@Arbitrary(.dynamic)` to the exceptions.
26+
- Default value generation for properties in `@Mock` and `@AnyMockable`: standard types, collections, and tuples.
27+
- Generic type support in `@Mock` and `@AnyMockable`.
28+
29+
# 2.0.3
30+
31+
## Fixed
32+
33+
- `Sendable` protocol support in `MockMacro`.
34+
35+
# 2.0.2
36+
37+
## Added
38+
39+
- Default value generation for Swift or Foundation types and closures in the `Arbitrary` macro.
40+
41+
# 2.0.1
42+
43+
## Added
44+
45+
- Full nested type path generation for properties in the `Arbitrary` macro. For example, `One.Two.Three`.
46+
47+
# 2.0.0
48+
49+
## Added
50+
51+
- `Mock` now generates initializers if defined in the protocol.
52+
- Actor support in `Mock`. Property setters are now generated for mutable properties.
53+
- API Mock change: replaced `shouldBeInherited` parameter with `inheritability`.
54+
- Removed `Foundation` import requirement for `Mock` or `AnyMockable` macros.
55+
- `Mock` automatically applies weak modifier to delegate properties. Use `@Ignored` macro to override.
56+
- Added new README and GitLab wiki.
57+
58+
## Fixed
59+
60+
- `Arbitrary` now correctly generates default values for `Bool`.
61+
- Fixed return value type generation of `some` or `any` in `Mock` and `AnyMockable`.

Docs/AnyMockable.md

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# AnyMockable
2+
3+
The `AnyMockable` macro allows you to semi-automatically create a mock for any protocol.
4+
5+
1. Manually create a class, actor, or struct manually and add it to the desired protocol.
6+
2. Add a macro, methods, or properties to the created declaration. It automatically generates a mock nested class based on the implemented protocol and adds an object of this nested class.
7+
8+
To implement the methods, manually enable proxying of these methods for an object of the mock class.
9+
Example:
10+
11+
```
12+
protocol ParentService {
13+
func checkStatus() async throws -> Bool
14+
}
15+
16+
protocol IService: AnyActor, ParentService {
17+
var delegate: Delegate? { get async }
18+
var parent: ParentService { get async }
19+
20+
func upload(file: Data) async throws
21+
}
22+
23+
@AnyMockable
24+
actor IServiceMock: IService {
25+
weak var delegate: Delegate?
26+
@MockAccessor // Сгенерировано макросом
27+
var parent: ParentService
28+
29+
func upload(file: Data) async throws {
30+
try await mock.upload(file: file)
31+
}
32+
33+
func checkStatus() async throws -> Bool {
34+
try await mock.checkStatus()
35+
}
36+
37+
// Сгенерировано макросом
38+
internal let mock = Mock()
39+
40+
internal final class Mock {
41+
var underlyingParent: ParentService!
42+
43+
private let lock = AtomicLock()
44+
45+
// MARK: - upload
46+
47+
fileprivate func upload(file: Data) async throws {
48+
uploadFileCallsCount += 1
49+
uploadFileReceivedArguments.append(file)
50+
if let uploadFileError {
51+
throw uploadFileError
52+
}
53+
try await uploadFileClosure?(file)
54+
}
55+
var uploadFileCallsCount = 0
56+
var uploadFileReceivedArguments: [Data] = []
57+
var uploadFileError: Error?
58+
var uploadFileClosure: ((Data) async throws -> Void)?
59+
60+
// MARK: - checkStatus
61+
62+
fileprivate func checkStatus() async throws -> Bool {
63+
checkStatusCallsCount += 1
64+
if let checkStatusError {
65+
throw checkStatusError
66+
}
67+
if let checkStatusClosure {
68+
return try await checkStatusClosure()
69+
} else {
70+
return checkStatusReturnValue
71+
}
72+
}
73+
var checkStatusCallsCount = 0
74+
var checkStatusError: Error?
75+
var checkStatusClosure: (() async throws -> Bool )?
76+
var checkStatusReturnValue: Bool!
77+
}
78+
}
79+
80+
// Generated by macro
81+
extension IServiceMock: ProxyableMock { }
82+
83+
```
84+
85+
Using the created mock:
86+
87+
```
88+
func test() {
89+
let mock = IServiceMock()
90+
91+
...
92+
93+
XCTAssertEqual(mock.checkStatusCallsCount, 1)
94+
XCTAssertNil(mock.checkStatusError)
95+
}
96+
97+
```
98+
99+
### `@MockAccessor`
100+
The `MockAccessor` auxiliary macro is automatically added to each non-optional property in the mock declaration and uses a getter and setter to proxy a similar underlying property from the internal mock class.
101+
102+
```
103+
@MockAccessor var parent: ParentService
104+
```
105+
106+
This macro is expanded as follows:
107+
108+
```
109+
var parent: ParentService {
110+
get {
111+
mock.underlyingParent
112+
}
113+
set(newValue) {
114+
mock.underlyingParent = newValue
115+
}
116+
}
117+
118+
```
119+
120+
Don't use the `@MockAccessor` macro manually. It's automatically added to each non-optional property of the mock declaration.

Docs/Arbitrary.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Arbitrary
2+
3+
The `Arbitrary` macro generates an enum with the `Arbitrary` method inside, which returns the object of the declaration it's attached to. The macro can be attached to structures, classes, actors, and protocols. The macro inside recursively calls the `.arbitrary()` method. You can call the macro with the `.dynamic` and `.static` arguments. When using: - the `.static` argument, some `Foundation` types remain the same; - the `.dynamic` argument, the types generate different values. The `.static` argument is used by default.
4+
5+
The macro also additionally generates a separated initializer if necessary.
6+
7+
```
8+
@Arbitrary(.dynamic)
9+
public struct Model {
10+
let int: Int
11+
let string: String
12+
let otherModel: OtherModel
13+
14+
/// Generated initializer
15+
public init(int: Int, string: String, otherModel: OtherModel) {
16+
self.int = int
17+
self.string = string
18+
self.otherModel = otherModel
19+
}
20+
}
21+
22+
/// Generated enum
23+
public enum ModelArbitrary {
24+
public static func arbitrary(
25+
int: Int = .arbitrary(.dynamic),
26+
string: String = .arbitrary(.dynamic),
27+
otherModel: OtherModel = OtherModelArbitrary.arbitrary()
28+
) -> Model {
29+
Model(int: int, string: string, otherModel: otherModel)
30+
}
31+
}
32+
33+
```
34+
35+
### `Arbitrary` for protocols
36+
The `Arbitrary` macro should be attached to the protocol together with the `@Mock` macro, since Mock is used to create the declaration object.
37+
Example with the protocol:
38+
39+
```
40+
@Mock
41+
@Arbitrary
42+
protocol Service {
43+
var otherService: OtherService { get set }
44+
}
45+
46+
/// Generated enum
47+
enum ServiceArbitrary {
48+
static func arbitrary(otherService: OtherService = OtherServiceArbitrary.arbitrary()) -> Service {
49+
let mock = ServiceMock()
50+
mock.otherService = otherService
51+
return mock
52+
}
53+
}
54+
55+
```
56+
57+
### `@Ignored` macro
58+
The `Ignored` auxiliary macro is used to indicate properties for which you don't need to generate a value.
59+
60+
Example with `@Ignored`:
61+
62+
```
63+
class Object {}
64+
65+
enum Enumeration { }
66+
67+
class Wrapper<T: AnyObject> {
68+
weak var wrapped: T?
69+
}
70+
71+
@Arbitrary
72+
class Model {
73+
let id: UUID
74+
@Ignored let enumeration: Enumeration
75+
@Ignored let wrappedObject: Wrapper<Object>
76+
77+
/// Generated initializer
78+
init(id: UUID, enumeration: Enumeration, wrappedObject: Wrapper<Object>) {
79+
self.id = id
80+
self.enumeration = enumeration
81+
self.wrappedObject = wrappedObject
82+
}
83+
}
84+
85+
/// Generated enum
86+
enum ModelArbitrary {
87+
static func arbitrary(id: UUID = .arbitrary(), enumeration: Enumeration, wrappedObject: Wrapper<Object>) -> Model {
88+
Model(id: id, enumeration: enumeration, wrappedObject: wrappedObject)
89+
}
90+
}
91+
92+
```
93+
94+
The `Arbitrary` macro doesn't support enum. For enum properties, use the `@Ignored` macro.

Docs/AutoEquatable.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# AutoEquatable
2+
3+
The `AutoEquatable` macro allows you to automatically subscribe to the `Equatable` protocol and add it to the extension.
4+
Class example:
5+
6+
```
7+
@AutoEquatable
8+
class Model {
9+
let name: String
10+
let id: UUID
11+
let base: String
12+
}
13+
```
14+
15+
You can expand the macro as:
16+
17+
```
18+
extension Model: Equatable {
19+
static func == (lhs: Model, rhs: Model) -> Bool {
20+
lhs.name == rhs.name && lhs.id == rhs.id && lhs.base && rhs.base
21+
}
22+
}
23+
24+
```
25+
26+
Enum example:
27+
28+
```
29+
@AutoEquatable
30+
enum Enumeration {
31+
case first
32+
case second(String, Int)
33+
case third(arg: String)
34+
35+
var property: Bool {
36+
.random()
37+
}
38+
}
39+
```
40+
41+
### `@Ignored` macro
42+
43+
The `@Ignored` auxiliary macro allows you to mark a property as ignored so that it isn't taken into account when checking equivalence.
44+
45+
```
46+
@AutoEquatable
47+
class ViewModel {
48+
let id: UUID
49+
let service: ViewModelService
50+
@Ignored
51+
weak var delegate: IViewModelDelegate?
52+
}
53+
54+
// Generated code
55+
extension ViewModel: Equatable {
56+
static func == (lhs: Model, rhs: Model) -> Bool {
57+
lhs.id == rhs.id && lhs.service == rhs.service
58+
}
59+
}
60+
61+
```

0 commit comments

Comments
 (0)