Skip to content

Commit c5e9648

Browse files
committed
Add new post
1 parent cf1858b commit c5e9648

3 files changed

Lines changed: 143 additions & 0 deletions

File tree

169 KB
Loading
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
---
2+
title: 'Concurrency-Safe Testing in Swift 6.1 with @TaskLocal and Test Scoping'
3+
date: 2025-05-07
4+
tags: ['Swift', 'Swift Testing', 'Testing', 'unit testing']
5+
cover:
6+
image: 'images/cover.png'
7+
alt: 'Concurrency-Safe Testing in Swift 6.1 with @TaskLocal and Test Scoping'
8+
---
9+
10+
Today’s example shows a static property providing the current date.
11+
12+
Perfect mechanism when we don’t want to inject it everywhere where it’s used.
13+
14+
How to test it? Check it out ⤵️
15+
16+
## Initial setup
17+
18+
```swift
19+
let currentDateFormatter: DateFormatter = {
20+
let formatter = DateFormatter()
21+
formatter.dateStyle = .short
22+
formatter.timeStyle = .none
23+
return formatter
24+
}()
25+
26+
var currentDateFormatted: String {
27+
currentDateFormatter.string(from: DateEnvironment.currentDate())
28+
}
29+
30+
enum DateEnvironment {
31+
static var currentDate: () -> Date = Date.init
32+
}
33+
34+
extension Date {
35+
// 16.04.2025
36+
static let sixteenthOfApril = Date(timeIntervalSince1970: 1744840515)
37+
}
38+
```
39+
40+
## Old way - XCTest
41+
42+
- Override the static property in tests.
43+
- Works, but not concurrency-safe (Ok, for XCTest because test ran serially).
44+
45+
```swift
46+
class CurrentDateFormatterTests: XCTestCase {
47+
func test_dateFormatting() { //
48+
DateEnvironment.currentDate = { .sixteenthOfApril }
49+
XCTAssertEqual(currentDateFormatted, "16.04.2025")
50+
}
51+
}
52+
```
53+
54+
## Swift Testing before Swift 6.1
55+
56+
```swift
57+
enum DateEnvironment {
58+
@TaskLocal static var currentDate: () -> Date = Date.init
59+
}
60+
61+
struct CurrentDateFormatterTests {
62+
@Test
63+
func dateFormatting() { //
64+
DateEnvironment.$currentDate
65+
.withValue({ .sixteenthOfApril }) {
66+
#expect(currentDateFormatted == "16.04.2025")
67+
}
68+
}
69+
}
70+
```
71+
72+
Why @TaskLocal?
73+
- Scoped per async Task.
74+
- No global side-effects
75+
- Safe for parallel tests
76+
77+
### What is @TaskLocal?
78+
- Property wrapper for per-Task storage.
79+
- Default value is returned when no override is applied.
80+
- withValue(_:, operation:) - binds a new value just for that Task.
81+
- Child Task { ... } inherits override.
82+
- Detached - Task.detached { ... } does not inherit override.
83+
84+
```swift
85+
DateEnvironment.$currentDate.withValue({ .sixteenthOfApril }) {
86+
print(DateEnvironment.currentDate()) // 16.04.2025
87+
Task {
88+
print(DateEnvironment.currentDate()) // 16.04.2025 (inherited)
89+
}
90+
Task.detached {
91+
print(DateEnvironment.currentDate()) // today’s date (no inheritance)
92+
}
93+
}
94+
```
95+
96+
## New in Swift 6.1: Test Scoping in Action
97+
98+
```swift
99+
enum DateEnvironment {
100+
@TaskLocal static var currentDate: () -> Date = Date.init
101+
}
102+
103+
struct FixedDateTrait: TestTrait, TestScoping {
104+
let date: Date
105+
106+
func provideScope(
107+
for test: Test,
108+
testCase: Test.Case?,
109+
performing function: () async throws -> Void
110+
) async throws {
111+
try await DateEnvironment.$currentDate
112+
.withValue({ date }, operation: function)
113+
}
114+
}
115+
116+
extension Trait where Self == FixedDateTrait {
117+
static func fixedDate(_ date: Date) -> Self {
118+
.init(date: date)
119+
}
120+
}
121+
122+
struct CurrentDateFormatterTests {
123+
@Test(.fixedDate(.sixteenthOfApril))
124+
func dateFormatting() { //
125+
#expect(currentDateFormatted == "16.04.2025")
126+
}
127+
}
128+
```
129+
130+
## Final advice
131+
132+
**Stay Curious**
133+
134+
Use @TaskLocal and Test Scoping for deterministic, parallel-safe tests.
135+
Enjoy faster feedback looks and cleaner code.
136+
137+
**Remember** - Learning never stops - keep experimenting with new features!
138+
139+
PDF version ⤵️
140+
141+
[Concurrency-Safe Testing in Swift 6.1 with @TaskLocal and Test Scoping](resources/document.pdf)
142+
143+
{{< footer >}}
Binary file not shown.

0 commit comments

Comments
 (0)