You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+201-3Lines changed: 201 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -3,7 +3,7 @@
3
3
</p>
4
4
5
5
<h1align="center">Probing</h1>
6
-
<palign="center">Breakpoints for Swift Testing - precise control over side effects and execution suspension at any point.</p>
6
+
<palign="center">Breakpoints for Swift Testing - precise control over side effects and fully observable state transitions in asynchronous functions</p>
@@ -29,7 +30,7 @@ to inspect just the final output of the function. Inspecting the internal state
29
30
is equally important but notoriously difficult.
30
31
-**Non-determinism**: `Task` instances run concurrently and may complete in different orders each time, leading to unpredictable states.
31
32
Even with full code coverage, there’s no guarantee that all execution paths have been reached, and it's' difficult to reason about what remains untested.
32
-
-**Limited runtime control**: Once an asynchronous function is running, influencing its behavior becomes nearly impossible.
33
+
-**Limited runtime control**: Once an asynchronous function is running, influencing its behavior becomes hard.
33
34
This limitation pushes developers to rely on ahead-of-time setups, like intricate mocks, which add complexity and reduce clarity of the test.
34
35
35
36
Over the years, the Swift community has introduced a number of tools to address these challenges, each with its own strengths:
@@ -56,7 +57,7 @@ conceptually similar to breakpoints, but accessible and targetable from your tes
56
57
You can also define **effects**, which make `Task` instances controllable and predictable.
57
58
58
59
Then, with the help of `ProbeTesting`, you write a sequence of **dispatches** that advance your program to a desired state.
59
-
This flattens the execution hierarchy of side effects, allowing you to write tests from the user’s perspective,
60
+
This flattens the execution tree of side effects, allowing you to write tests from the user’s perspective,
60
61
as a clear and deterministic flow of events:
61
62
62
63
```swift
@@ -95,6 +96,203 @@ Full documentation is available on the Swift Package Index:
95
96
96
97
You can download the `ProbingPlayground` sample project from its [GitHub page](https://github.com/NSFatalError/ProbingPlayground).
97
98
99
+
## Examples
100
+
101
+
The `CHANGED` and `ADDED` comments highlight how the view model in the examples
102
+
has been adapted to support testing with `ProbeTesting`. As you can see, the required changes
103
+
are small and don’t require any architectural shift.
104
+
105
+
### Observing State Transitions
106
+
107
+
```swift
108
+
// ViewModel.swift
109
+
110
+
funcuploadImage(_item: ImageItem) async {
111
+
do {
112
+
uploadState = .uploading
113
+
await#probe() // ADDED
114
+
let image =tryawait item.loadImage()
115
+
let processedImage =tryawait processor.processImage(image)
116
+
tryawait uploader.uploadImage(processedImage)
117
+
uploadState = .success
118
+
} catch {
119
+
uploadState = .error
120
+
}
121
+
122
+
await#probe() // ADDED
123
+
try?await Task.sleep(for: .seconds(3))
124
+
uploadState =nil
125
+
}
126
+
```
127
+
128
+
```swift
129
+
// ViewModelTests.swift
130
+
131
+
@Test
132
+
functestUploadingImage() asyncthrows {
133
+
tryawaitwithProbing {
134
+
await viewModel.uploadImage(ImageMock())
135
+
} dispatchedBy: { dispatcher in
136
+
#expect(viewModel.uploadState==nil)
137
+
138
+
tryawait dispatcher.runUpToProbe()
139
+
#expect(uploader.uploadImageCallsCount==0)
140
+
#expect(viewModel.uploadState== .uploading)
141
+
142
+
tryawait dispatcher.runUpToProbe()
143
+
#expect(uploader.uploadImageCallsCount==1)
144
+
#expect(viewModel.uploadState== .success)
145
+
146
+
tryawait dispatcher.runUntilExitOfBody()
147
+
#expect(viewModel.uploadState==nil)
148
+
}
149
+
}
150
+
```
151
+
152
+
### Just-in-Time Mocking
153
+
154
+
```swift
155
+
// ViewModel.swift
156
+
157
+
funcupdateLocation() async {
158
+
locationState = .unknown
159
+
await#probe() // ADDED
160
+
161
+
do {
162
+
fortryawait update in locationProvider.getUpdates() {
0 commit comments