Skip to content

Commit 371c050

Browse files
authored
Merge pull request #126 from Matejkob/readme
Update README
2 parents cb44ec5 + 496478c commit 371c050

File tree

1 file changed

+17
-15
lines changed

1 file changed

+17
-15
lines changed

README.md

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -89,27 +89,31 @@ func testFetchConfig() async throws {
8989
}
9090
```
9191

92+
## Advanced Usage
93+
9294
### Generic Functions
93-
Generic functions are supported, but require some care to use, as they get treated a little differently from other functionality.
9495

95-
Given a function:
96+
Spyable supports generic functions, but their implementation involves special handling. Due to limitations in Swift, generic parameters in a function are replaced with `Any` in the spy class to store arguments, return values, and closures.
97+
98+
For example:
9699

97100
```swift
98101
func foo<T, U>(_ bar: T) -> U
99102
```
100103

101-
The following will be created in a spy:
104+
Generates the following spy:
102105

103106
```swift
104107
class MyProtocolSpy: MyProtocol {
105108
var fooCallsCount = 0
106109
var fooCalled: Bool {
107-
return fooCallsCount > 0
110+
return fooCallsCount > 0
108111
}
109112
var fooReceivedBar: Any?
110113
var fooReceivedInvocations: [Any] = []
111114
var fooReturnValue: Any!
112115
var fooClosure: ((Any) -> Any)?
116+
113117
func foo<T, U>(_ bar: T) -> U {
114118
fooCallsCount += 1
115119
fooReceivedBar = (bar)
@@ -122,12 +126,13 @@ class MyProtocolSpy: MyProtocol {
122126
}
123127
}
124128
```
125-
Uses of `T` and `U` get substituted with `Any` because generics specified only by a function can't be stored as a property in the function's class. Using `Any` lets us store injected closures, invocations, etc.
126129

127-
Force casts get used to turn an injected closure or returnValue property from `Any` into an expected type. This means that *it's essential that expected types match up with values given to these injected properties*.
130+
#### Important Notes:
128131

129-
##### Example:
130-
Given the following code:
132+
1. **Type Matching**:
133+
Ensure the expected types align with the injected `returnValue` or `closure`. Mismatched types will result in runtime crashes due to force casting.
134+
135+
2. **Example**:
131136

132137
```swift
133138
@Spyable
@@ -144,24 +149,21 @@ struct ViewModel {
144149
}
145150
```
146151

147-
A test for ViewModel's `wrapData()` function could look like this:
152+
Test for `wrapData()`:
148153

149154
```swift
150155
func testWrapData() {
151-
// Important: When using generics, mocked return value types must match the types that are being returned in the use of the spy.
152156
serviceSpy.wrapDataInArrayReturnValue = [123]
153157
XCTAssertEqual(sut.wrapData(1), [123])
154158
XCTAssertEqual(serviceSpy.wrapDataInArrayReceivedData as? Int, 1)
155159

156-
// ⚠️ The following would be incorrect, and cause a fatal error, because an Array<String> will be returned by wrapData(), but here we'd be providing an Array<Int> to wrapDataInArrayReturnValue. ⚠️
157-
// XCTAssertEqual(sut.wrapData("hi"), ["hello"])
160+
// Incorrect usage: mismatched type
161+
// serviceSpy.wrapDataInArrayReturnValue = ["hello"] // ⚠️ Causes runtime error
158162
}
159163
```
160164

161165
> [!TIP]
162-
> If you see a crash at force casting within a spy's generic function implementation, it most likely means that types are mismatched.
163-
164-
## Advanced Usage
166+
> If you see a crash in the generic function, check the type alignment between expected and injected values.
165167
166168
### Restricting Spy Availability
167169

0 commit comments

Comments
 (0)