-
Notifications
You must be signed in to change notification settings - Fork 299
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d08b3bc
commit 4b55f51
Showing
17 changed files
with
974 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
## [Point-Free](https://www.pointfree.co) | ||
|
||
> #### This directory contains code from Point-Free Episode: [Reliable Async Tests: The Problem](https://www.pointfree.co/episodes/ep238-reliable-async-tests) | ||
> | ||
> While Swift provides wonderful tools for writing async code, there are gaps in its tools for testing it. Let’s explore the tools it _does_ provide to show where they succeed, and where they fall short. |
513 changes: 513 additions & 0 deletions
513
...bly-testing-async-pt1/ReliablyTestingAsync/ReliablyTestingAsync.xcodeproj/project.pbxproj
Large diffs are not rendered by default.
Oops, something went wrong.
7 changes: 7 additions & 0 deletions
7
...yTestingAsync/ReliablyTestingAsync.xcodeproj/project.xcworkspace/contents.xcworkspacedata
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
8 changes: 8 additions & 0 deletions
8
.../ReliablyTestingAsync.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>IDEDidComputeMac32BitWarning</key> | ||
<true/> | ||
</dict> | ||
</plist> |
106 changes: 106 additions & 0 deletions
106
...Async/ReliablyTestingAsync.xcodeproj/xcshareddata/xcschemes/ReliablyTestingAsync.xcscheme
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<Scheme | ||
LastUpgradeVersion = "1500" | ||
version = "1.7"> | ||
<BuildAction | ||
parallelizeBuildables = "YES" | ||
buildImplicitDependencies = "YES"> | ||
<BuildActionEntries> | ||
<BuildActionEntry | ||
buildForTesting = "YES" | ||
buildForRunning = "YES" | ||
buildForProfiling = "YES" | ||
buildForArchiving = "YES" | ||
buildForAnalyzing = "YES"> | ||
<BuildableReference | ||
BuildableIdentifier = "primary" | ||
BlueprintIdentifier = "CAC2DD7C2A30B827007675BD" | ||
BuildableName = "ReliablyTestingAsync.app" | ||
BlueprintName = "ReliablyTestingAsync" | ||
ReferencedContainer = "container:ReliablyTestingAsync.xcodeproj"> | ||
</BuildableReference> | ||
</BuildActionEntry> | ||
</BuildActionEntries> | ||
</BuildAction> | ||
<TestAction | ||
buildConfiguration = "Debug" | ||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||
shouldUseLaunchSchemeArgsEnv = "YES"> | ||
<TestPlans> | ||
<TestPlanReference | ||
reference = "container:ReliablyTestingAsyncTests/ReliablyTestingAsync.xctestplan" | ||
default = "YES"> | ||
</TestPlanReference> | ||
</TestPlans> | ||
<Testables> | ||
<TestableReference | ||
skipped = "NO" | ||
parallelizable = "YES"> | ||
<BuildableReference | ||
BuildableIdentifier = "primary" | ||
BlueprintIdentifier = "CAC2DD8C2A30B829007675BD" | ||
BuildableName = "ReliablyTestingAsyncTests.xctest" | ||
BlueprintName = "ReliablyTestingAsyncTests" | ||
ReferencedContainer = "container:ReliablyTestingAsync.xcodeproj"> | ||
</BuildableReference> | ||
</TestableReference> | ||
<TestableReference | ||
skipped = "NO" | ||
parallelizable = "YES"> | ||
<BuildableReference | ||
BuildableIdentifier = "primary" | ||
BlueprintIdentifier = "CAC2DD962A30B829007675BD" | ||
BuildableName = "ReliablyTestingAsyncUITests.xctest" | ||
BlueprintName = "ReliablyTestingAsyncUITests" | ||
ReferencedContainer = "container:ReliablyTestingAsync.xcodeproj"> | ||
</BuildableReference> | ||
</TestableReference> | ||
</Testables> | ||
</TestAction> | ||
<LaunchAction | ||
buildConfiguration = "Debug" | ||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | ||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | ||
launchStyle = "0" | ||
useCustomWorkingDirectory = "NO" | ||
ignoresPersistentStateOnLaunch = "NO" | ||
debugDocumentVersioning = "YES" | ||
debugServiceExtension = "internal" | ||
allowLocationSimulation = "YES"> | ||
<BuildableProductRunnable | ||
runnableDebuggingMode = "0"> | ||
<BuildableReference | ||
BuildableIdentifier = "primary" | ||
BlueprintIdentifier = "CAC2DD7C2A30B827007675BD" | ||
BuildableName = "ReliablyTestingAsync.app" | ||
BlueprintName = "ReliablyTestingAsync" | ||
ReferencedContainer = "container:ReliablyTestingAsync.xcodeproj"> | ||
</BuildableReference> | ||
</BuildableProductRunnable> | ||
</LaunchAction> | ||
<ProfileAction | ||
buildConfiguration = "Release" | ||
shouldUseLaunchSchemeArgsEnv = "YES" | ||
savedToolIdentifier = "" | ||
useCustomWorkingDirectory = "NO" | ||
debugDocumentVersioning = "YES"> | ||
<BuildableProductRunnable | ||
runnableDebuggingMode = "0"> | ||
<BuildableReference | ||
BuildableIdentifier = "primary" | ||
BlueprintIdentifier = "CAC2DD7C2A30B827007675BD" | ||
BuildableName = "ReliablyTestingAsync.app" | ||
BlueprintName = "ReliablyTestingAsync" | ||
ReferencedContainer = "container:ReliablyTestingAsync.xcodeproj"> | ||
</BuildableReference> | ||
</BuildableProductRunnable> | ||
</ProfileAction> | ||
<AnalyzeAction | ||
buildConfiguration = "Debug"> | ||
</AnalyzeAction> | ||
<ArchiveAction | ||
buildConfiguration = "Release" | ||
revealArchiveInOrganizer = "YES"> | ||
</ArchiveAction> | ||
</Scheme> |
10 changes: 10 additions & 0 deletions
10
0238-reliably-testing-async-pt1/ReliablyTestingAsync/ReliablyTestingAsync/App.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import SwiftUI | ||
|
||
@main | ||
struct ReliablyTestingAsyncApp: App { | ||
var body: some Scene { | ||
WindowGroup { | ||
ContentView(model: NumberFactModel()) | ||
} | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
...iablyTestingAsync/ReliablyTestingAsync/Assets.xcassets/AccentColor.colorset/Contents.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"colors" : [ | ||
{ | ||
"idiom" : "universal" | ||
} | ||
], | ||
"info" : { | ||
"author" : "xcode", | ||
"version" : 1 | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
...eliablyTestingAsync/ReliablyTestingAsync/Assets.xcassets/AppIcon.appiconset/Contents.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"images" : [ | ||
{ | ||
"idiom" : "universal", | ||
"platform" : "ios", | ||
"size" : "1024x1024" | ||
} | ||
], | ||
"info" : { | ||
"author" : "xcode", | ||
"version" : 1 | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
...testing-async-pt1/ReliablyTestingAsync/ReliablyTestingAsync/Assets.xcassets/Contents.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"info" : { | ||
"author" : "xcode", | ||
"version" : 1 | ||
} | ||
} |
89 changes: 89 additions & 0 deletions
89
0238-reliably-testing-async-pt1/ReliablyTestingAsync/ReliablyTestingAsync/ContentView.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import Dependencies | ||
import SwiftUI | ||
|
||
struct NumberFactClient { | ||
var fact: @Sendable (Int) async throws -> String | ||
} | ||
|
||
extension NumberFactClient: DependencyKey { | ||
static let liveValue = Self { number in | ||
try await Task.sleep(for: .seconds(1)) | ||
return try await String( | ||
decoding: URLSession.shared.data(from: URL(string: "http://numbersapi.com/\(number)")!).0, | ||
as: UTF8.self | ||
) | ||
} | ||
} | ||
|
||
extension DependencyValues { | ||
var numberFact: NumberFactClient { | ||
get { self[NumberFactClient.self] } | ||
set { self[NumberFactClient.self] = newValue } | ||
} | ||
} | ||
|
||
@MainActor | ||
class NumberFactModel: ObservableObject { | ||
@Dependency(\.numberFact) var numberFact | ||
|
||
@Published var count = 0 | ||
@Published var fact: String? | ||
@Published var isLoading = false | ||
|
||
func incrementButtonTapped() { | ||
self.fact = nil | ||
self.count += 1 | ||
} | ||
func decrementButtonTapped() { | ||
self.fact = nil | ||
self.count -= 1 | ||
} | ||
func getFactButtonTapped() async { | ||
self.isLoading = true | ||
defer { self.isLoading = false } | ||
|
||
self.fact = nil | ||
do { | ||
self.fact = try await self.numberFact.fact(self.count) | ||
} catch { | ||
// TODO: handle error | ||
} | ||
} | ||
} | ||
|
||
struct ContentView: View { | ||
@ObservedObject var model: NumberFactModel | ||
|
||
var body: some View { | ||
Form { | ||
Section { | ||
HStack { | ||
Button("-") { self.model.decrementButtonTapped() } | ||
Text("\(self.model.count)") | ||
Button("+") { self.model.incrementButtonTapped() } | ||
} | ||
} | ||
.buttonStyle(.plain) | ||
|
||
HStack { | ||
Button("Get fact") { | ||
Task { await self.model.getFactButtonTapped() } | ||
} | ||
if self.model.isLoading { | ||
Spacer() | ||
ProgressView() | ||
} | ||
} | ||
|
||
if let fact = self.model.fact { | ||
Text(fact) | ||
} | ||
} | ||
} | ||
} | ||
|
||
struct ContentPreviews: PreviewProvider { | ||
static var previews: some View { | ||
ContentView(model: NumberFactModel()) | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
0238-reliably-testing-async-pt1/ReliablyTestingAsync/ReliablyTestingAsync/Info.plist
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>NSAppTransportSecurity</key> | ||
<dict> | ||
<key>NSAllowsArbitraryLoads</key> | ||
<true/> | ||
</dict> | ||
</dict> | ||
</plist> |
6 changes: 6 additions & 0 deletions
6
...lyTestingAsync/ReliablyTestingAsync/Preview Content/Preview Assets.xcassets/Contents.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"info" : { | ||
"author" : "xcode", | ||
"version" : 1 | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
...iably-testing-async-pt1/ReliablyTestingAsync/ReliablyTestingAsyncTests/LockIsolated.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import Foundation | ||
|
||
public final class LockIsolated<Value>: @unchecked Sendable { | ||
private var _value: Value | ||
private let lock = NSRecursiveLock() | ||
public init(_ value: @autoclosure @Sendable () throws -> Value) rethrows { | ||
self._value = try value() | ||
} | ||
public func withValue<T: Sendable>( | ||
_ operation: (inout Value) throws -> T | ||
) rethrows -> T { | ||
try self.lock.withLock { | ||
var value = self._value | ||
defer { self._value = value } | ||
return try operation(&value) | ||
} | ||
} | ||
} | ||
|
||
extension LockIsolated where Value: Sendable { | ||
public var value: Value { | ||
self.lock.withLock { | ||
self._value | ||
} | ||
} | ||
} |
71 changes: 71 additions & 0 deletions
71
...sting-async-pt1/ReliablyTestingAsync/ReliablyTestingAsyncTests/NumberFactModelTests.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import Dependencies | ||
import XCTest | ||
@testable import ReliablyTestingAsync | ||
|
||
@MainActor | ||
final class NumberFactModelTests: XCTestCase { | ||
func testIncrementDecrement() { | ||
let model = NumberFactModel() | ||
model.incrementButtonTapped() | ||
XCTAssertEqual(model.count, 1) | ||
model.decrementButtonTapped() | ||
XCTAssertEqual(model.count, 0) | ||
} | ||
|
||
func testGetFact() async { | ||
let model = withDependencies { | ||
$0.numberFact.fact = { "\($0) is a good number." } | ||
} operation: { | ||
NumberFactModel() | ||
} | ||
await model.getFactButtonTapped() | ||
XCTAssertEqual(model.fact, "0 is a good number.") | ||
|
||
model.incrementButtonTapped() | ||
XCTAssertEqual(model.fact, nil) | ||
|
||
await model.getFactButtonTapped() | ||
XCTAssertEqual(model.fact, "1 is a good number.") | ||
} | ||
|
||
func testFactClearsOut() async { | ||
let fact = AsyncStream.makeStream(of: String.self) | ||
|
||
let model = withDependencies { | ||
$0.numberFact.fact = { _ in | ||
await fact.stream.first(where: { _ in true })! | ||
} | ||
} operation: { | ||
NumberFactModel() | ||
} | ||
model.fact = "An old fact about 0." | ||
|
||
let task = Task { await model.getFactButtonTapped() } | ||
await Task.yield() | ||
XCTAssertEqual(model.fact, nil) | ||
fact.continuation.yield("0 is a good number.") | ||
await task.value | ||
XCTAssertEqual(model.fact, "0 is a good number.") | ||
} | ||
|
||
func testFactIsLoading() async { | ||
let fact = AsyncStream.makeStream(of: String.self) | ||
|
||
let model = withDependencies { | ||
$0.numberFact.fact = { _ in | ||
await fact.stream.first(where: { _ in true })! | ||
} | ||
} operation: { | ||
NumberFactModel() | ||
} | ||
model.fact = "An old fact about 0." | ||
|
||
let task = Task { await model.getFactButtonTapped() } | ||
await Task.yield() | ||
XCTAssertEqual(model.isLoading, true) | ||
fact.continuation.yield("0 is a good number.") | ||
await task.value | ||
XCTAssertEqual(model.fact, "0 is a good number.") | ||
XCTAssertEqual(model.isLoading, false) | ||
} | ||
} |
Oops, something went wrong.