Skip to content

Commit f709ea1

Browse files
authored
[url_launcher_ios] Migrate XCTest to Swift Testing (#10780)
Part of flutter/flutter#180787 Add some test parameterization. ``` ◇ Test run started. ↳ Testing Library Version: 102 (arm64-apple-ios13.0-simulator) ◇ Suite URLLauncherTests started. ◇ Test canLaunchFailureWithInvalidURL() started. ◇ Test canLaunch(url:expected:) started. ◇ Test launchWithUniversalLinks() started. ◇ Test launch(url:expected:) started. ◇ Test launchSafariViewControllerWithClose() started. ◇ Test launchSafariViewControllerFailureWithNoViewPresenter() started. ◇ Test launchWithoutUniversalLinks() started. ◇ Test launchFailureWithInvalidURL() started. ◇ Passing 2 arguments url → "good://url", expected → .success to launch(url:expected:) ◇ Passing 2 arguments url → "bad://url", expected → .failure to launch(url:expected:) 2026-01-12 12:53:39.222409-0800 Runner[62355:494810] [Warning] Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior (<SFSafariViewController: 0x1018cd200>) ◇ Passing 2 arguments url → "bad://url", expected → .failure to canLaunch(url:expected:) ◇ Passing 2 arguments url → "good://url", expected → .success to canLaunch(url:expected:) ✔ Test canLaunchFailureWithInvalidURL() passed after 0.001 seconds. ✔ Test launchWithUniversalLinks() passed after 0.001 seconds. ✔ Test launchSafariViewControllerWithClose() passed after 0.042 seconds. ✔ Test launchWithoutUniversalLinks() passed after 0.042 seconds. ✔ Test launchSafariViewControllerFailureWithNoViewPresenter() passed after 0.042 seconds. ✔ Test launchFailureWithInvalidURL() passed after 0.042 seconds. ​​​​✔ Test launch(url:expected:) passed after 0.043 seconds. ✔ Test canLaunch(url:expected:) passed after 0.043 seconds. ✔ Suite URLLauncherTests passed after 0.043 seconds. ✔ Test run with 8 tests passed after 0.043 seconds. ``` https://logs.chromium.org/logs/flutter/buildbucket/cr-buildbucket/8692848274232556065/+/u/Run_package_tests/native_test/stdout Adding CHANGELOG override per #10761 (comment) ## Pre-Review Checklist **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
1 parent 6350518 commit f709ea1

File tree

2 files changed

+89
-104
lines changed

2 files changed

+89
-104
lines changed

packages/url_launcher/url_launcher_ios/example/ios/Runner.xcodeproj/project.pbxproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -650,7 +650,7 @@
650650
MTL_FAST_MATH = YES;
651651
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests;
652652
PRODUCT_NAME = "$(TARGET_NAME)";
653-
SWIFT_VERSION = 5.0;
653+
SWIFT_VERSION = 6.0;
654654
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner";
655655
};
656656
name = Debug;
@@ -671,7 +671,7 @@
671671
MTL_FAST_MATH = YES;
672672
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests;
673673
PRODUCT_NAME = "$(TARGET_NAME)";
674-
SWIFT_VERSION = 5.0;
674+
SWIFT_VERSION = 6.0;
675675
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner";
676676
};
677677
name = Release;

packages/url_launcher/url_launcher_ios/example/ios/RunnerTests/URLLauncherTests.swift

Lines changed: 87 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// found in the LICENSE file.
44

55
import Flutter
6-
import XCTest
6+
import Testing
77

88
@testable import url_launcher_ios
99

@@ -31,7 +31,8 @@ final class StubViewPresenterProvider: ViewPresenterProvider {
3131
}
3232
}
3333

34-
final class URLLauncherTests: XCTestCase {
34+
@MainActor
35+
struct URLLauncherTests {
3536

3637
private func createPlugin(
3738
launcher: FakeLauncher = FakeLauncher(), viewPresenter: ViewPresenter? = TestViewPresenter()
@@ -41,147 +42,131 @@ final class URLLauncherTests: XCTestCase {
4142
viewPresenterProvider: StubViewPresenterProvider(viewPresenter: viewPresenter))
4243
}
4344

44-
func testCanLaunchSuccess() {
45-
let result = createPlugin().canLaunchUrl(url: "good://url")
46-
XCTAssertEqual(result, .success)
45+
@Test(arguments: [
46+
("good://url", LaunchResult.success),
47+
("bad://url", .failure),
48+
])
49+
func canLaunch(url: String, expected: LaunchResult) {
50+
let result = createPlugin().canLaunchUrl(url: url)
51+
#expect(result == expected)
4752
}
4853

49-
func testCanLaunchFailure() {
50-
let result = createPlugin().canLaunchUrl(url: "bad://url")
51-
XCTAssertEqual(result, .failure)
52-
}
53-
54-
func testCanLaunchFailureWithInvalidURL() {
54+
@Test func canLaunchFailureWithInvalidURL() {
5555
let result = createPlugin().canLaunchUrl(url: "urls can't have spaces")
56-
5756
if urlParsingIsStrict() {
58-
XCTAssertEqual(result, .invalidUrl)
57+
#expect(result == .invalidUrl)
5958
} else {
60-
XCTAssertEqual(result, .failure)
59+
#expect(result == .failure)
6160
}
6261
}
6362

64-
func testLaunchSuccess() {
65-
let expectation = XCTestExpectation(description: "completion called")
66-
createPlugin().launchUrl(url: "good://url", universalLinksOnly: false) { result in
67-
switch result {
68-
case .success(let details):
69-
XCTAssertEqual(details, .success)
70-
case .failure(let error):
71-
XCTFail("Unexpected error: \(error)")
72-
}
73-
expectation.fulfill()
74-
}
75-
76-
wait(for: [expectation], timeout: 1)
77-
}
78-
79-
func testLaunchFailure() {
80-
let expectation = XCTestExpectation(description: "completion called")
81-
createPlugin().launchUrl(url: "bad://url", universalLinksOnly: false) { result in
82-
switch result {
83-
case .success(let details):
84-
XCTAssertEqual(details, .failure)
85-
case .failure(let error):
86-
XCTFail("Unexpected error: \(error)")
63+
@Test(arguments: [
64+
("good://url", LaunchResult.success),
65+
("bad://url", .failure),
66+
])
67+
func launch(url: String, expected: LaunchResult) async {
68+
await confirmation("completion called") { confirmed in
69+
createPlugin().launchUrl(url: url, universalLinksOnly: false) { result in
70+
switch result {
71+
case .success(let details):
72+
#expect(details == expected)
73+
case .failure(let error):
74+
Issue.record("Unexpected error: \(error)")
75+
}
76+
confirmed()
8777
}
88-
expectation.fulfill()
8978
}
90-
91-
wait(for: [expectation], timeout: 1)
9279
}
9380

94-
func testLaunchFailureWithInvalidURL() {
95-
let expectation = XCTestExpectation(description: "completion called")
96-
createPlugin().launchUrl(url: "urls can't have spaces", universalLinksOnly: false) { result in
97-
switch result {
98-
case .success(let details):
99-
if urlParsingIsStrict() {
100-
XCTAssertEqual(details, .invalidUrl)
101-
} else {
102-
XCTAssertEqual(details, .failure)
81+
@Test func launchFailureWithInvalidURL() async {
82+
await confirmation("completion called") { confirmed in
83+
createPlugin().launchUrl(url: "urls can't have spaces", universalLinksOnly: false) { result in
84+
switch result {
85+
case .success(let details):
86+
if urlParsingIsStrict() {
87+
#expect(details == .invalidUrl)
88+
} else {
89+
#expect(details == .failure)
90+
}
91+
case .failure(let error):
92+
Issue.record("Unexpected error: \(error)")
10393
}
104-
case .failure(let error):
105-
XCTFail("Unexpected error: \(error)")
94+
confirmed()
10695
}
107-
expectation.fulfill()
10896
}
109-
110-
wait(for: [expectation], timeout: 1)
11197
}
11298

113-
func testLaunchWithoutUniversalLinks() {
99+
@Test func launchWithoutUniversalLinks() async throws {
114100
let launcher = FakeLauncher()
115101
let plugin = createPlugin(launcher: launcher)
116102

117-
let expectation = XCTestExpectation(description: "completion called")
118-
plugin.launchUrl(url: "good://url", universalLinksOnly: false) { result in
119-
switch result {
120-
case .success(let details):
121-
XCTAssertEqual(details, .success)
122-
case .failure(let error):
123-
XCTFail("Unexpected error: \(error)")
103+
await confirmation("completion called") { confirmed in
104+
plugin.launchUrl(url: "good://url", universalLinksOnly: false) { result in
105+
switch result {
106+
case .success(let details):
107+
#expect(details == .success)
108+
case .failure(let error):
109+
Issue.record("Unexpected error: \(error)")
110+
}
111+
confirmed()
124112
}
125-
expectation.fulfill()
126113
}
127-
128-
wait(for: [expectation], timeout: 1)
129-
XCTAssertEqual(launcher.passedOptions?[.universalLinksOnly] as? Bool, false)
114+
let passedOptions = try #require(launcher.passedOptions)
115+
#expect(passedOptions[.universalLinksOnly] as? Bool == false)
130116
}
131117

132-
func testLaunchWithUniversalLinks() {
118+
@Test func launchWithUniversalLinks() async throws {
133119
let launcher = FakeLauncher()
134120
let plugin = createPlugin(launcher: launcher)
135121

136-
let expectation = XCTestExpectation(description: "completion called")
137-
plugin.launchUrl(url: "good://url", universalLinksOnly: true) { result in
138-
switch result {
139-
case .success(let details):
140-
XCTAssertEqual(details, .success)
141-
case .failure(let error):
142-
XCTFail("Unexpected error: \(error)")
122+
await confirmation("completion called") { confirmed in
123+
plugin.launchUrl(url: "good://url", universalLinksOnly: true) { result in
124+
switch result {
125+
case .success(let details):
126+
#expect(details == .success)
127+
case .failure(let error):
128+
Issue.record("Unexpected error: \(error)")
129+
}
130+
confirmed()
143131
}
144-
expectation.fulfill()
145132
}
146-
147-
wait(for: [expectation], timeout: 1)
148-
XCTAssertEqual(launcher.passedOptions?[.universalLinksOnly] as? Bool, true)
133+
let passedOptions = try #require(launcher.passedOptions)
134+
#expect(passedOptions[.universalLinksOnly] as? Bool == true)
149135
}
150136

151-
func testLaunchSafariViewControllerWithClose() {
137+
@Test func launchSafariViewControllerWithClose() async {
152138
let launcher = FakeLauncher()
153139
let viewPresenter = TestViewPresenter()
154140
let plugin = createPlugin(launcher: launcher, viewPresenter: viewPresenter)
155141

156-
let expectation = XCTestExpectation(description: "completion called")
157-
plugin.openUrlInSafariViewController(url: "https://flutter.dev") { result in
158-
switch result {
159-
case .success(let details):
160-
XCTAssertEqual(details, .dismissed)
161-
case .failure(let error):
162-
XCTFail("Unexpected error: \(error)")
142+
await confirmation("completion called") { confirmed in
143+
plugin.openUrlInSafariViewController(url: "https://flutter.dev") { result in
144+
switch result {
145+
case .success(let details):
146+
#expect(details == .dismissed)
147+
case .failure(let error):
148+
Issue.record("Unexpected error: \(error)")
149+
}
150+
confirmed()
163151
}
164-
expectation.fulfill()
152+
plugin.closeSafariViewController()
165153
}
166-
plugin.closeSafariViewController()
167-
wait(for: [expectation], timeout: 30)
168-
XCTAssertNotNil(viewPresenter.presentedController)
154+
#expect(viewPresenter.presentedController != nil)
169155
}
170156

171-
func testLaunchSafariViewControllerFailureWithNoViewPresenter() {
172-
let expectation = XCTestExpectation(description: "completion called")
173-
createPlugin(viewPresenter: nil).openUrlInSafariViewController(url: "https://flutter.dev") {
174-
result in
175-
switch result {
176-
case .success(let details):
177-
XCTAssertEqual(details, .noUI)
178-
case .failure(let error):
179-
XCTFail("Unexpected error: \(error)")
157+
@Test func launchSafariViewControllerFailureWithNoViewPresenter() async {
158+
await confirmation("completion called") { confirmed in
159+
createPlugin(viewPresenter: nil).openUrlInSafariViewController(url: "https://flutter.dev") {
160+
result in
161+
switch result {
162+
case .success(let details):
163+
#expect(details == .noUI)
164+
case .failure(let error):
165+
Issue.record("Unexpected error: \(error)")
166+
}
167+
confirmed()
180168
}
181-
expectation.fulfill()
182169
}
183-
184-
wait(for: [expectation], timeout: 1)
185170
}
186171

187172
}

0 commit comments

Comments
 (0)