Skip to content

Commit ee81bd4

Browse files
Merge pull request #171 from wwt/async-swiftui-tests
Refactored SwiftUI tests to use async/await APIs
2 parents db6e6ca + b8f59d6 commit ee81bd4

35 files changed

+2426
-2744
lines changed

.github/workflows/PR_CI.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,4 @@ jobs:
5656
- uses: actions/checkout@v2
5757
- name: Swiftlint
5858
run: bundle exec fastlane lint
59-
working-directory: ${{ env.working-directory }}
59+
working-directory: ${{ env.working-directory }}

ExampleApps/SwiftUIExample/SwiftUIExampleTests/UIKitInteropTests.swift

Lines changed: 120 additions & 119 deletions
Large diffs are not rendered by default.

ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/AccountInformationViewTests.swift

Lines changed: 57 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -34,31 +34,25 @@ final class AccountInformationViewTests: XCTestCase, WorkflowTestingReceiver {
3434

3535
private typealias MFAViewWorkflowView = WorkflowLauncher<WorkflowItem<MFAView, Never, MFAView>>
3636

37-
func testUpdatedAccountInformationView() throws {
38-
let exp = ViewHosting.loadView(AccountInformationView()).inspection.inspect { view in
39-
XCTAssertEqual(try view.find(ViewType.Text.self).string(), "Email: ")
40-
XCTAssertEqual(try view.find(ViewType.Text.self, skipFound: 1).string(), "[email protected]")
37+
func testUpdatedAccountInformationView() async throws {
38+
let view = try await AccountInformationView().hostAndInspect(with: \.inspection)
4139

42-
XCTAssertEqual(try view.find(ViewType.Text.self, skipFound: 2).string(), "Password: ")
43-
XCTAssertEqual(try view.find(ViewType.SecureField.self).input(), "supersecure")
40+
XCTAssertEqual(try view.find(ViewType.Text.self).string(), "Email: ")
41+
XCTAssertEqual(try view.find(ViewType.Text.self, skipFound: 1).string(), "[email protected]")
4442

45-
XCTAssertEqual(view.findAll(ViewType.Button.self).count, 2)
46-
}
47-
wait(for: [exp], timeout: TestConstant.timeout)
43+
XCTAssertEqual(try view.find(ViewType.Text.self, skipFound: 2).string(), "Password: ")
44+
XCTAssertEqual(try view.find(ViewType.SecureField.self).input(), "supersecure")
45+
46+
XCTAssertEqual(view.findAll(ViewType.Button.self).count, 2)
4847
}
4948

50-
func testAccountInformationCanLaunchUsernameWorkflowAgnostic() throws {
49+
func testAccountInformationCanLaunchUsernameWorkflowAgnostic() async throws {
5150
Self.workflowLaunchedData.removeAll()
52-
var accountInformation: InspectableView<ViewType.View<AccountInformationView>>!
53-
let exp = ViewHosting.loadView(AccountInformationView()).inspection.inspect { view in
54-
accountInformation = view
55-
XCTAssertFalse(try view.actualView().emailWorkflowLaunched)
56-
XCTAssertNoThrow(try view.find(ViewType.Button.self).tap())
57-
XCTAssert(try view.actualView().emailWorkflowLaunched)
58-
}
59-
wait(for: [exp], timeout: TestConstant.timeout)
6051

61-
XCTAssertNotNil(accountInformation)
52+
let accountInformation = try await AccountInformationView().hostAndInspect(with: \.inspection)
53+
XCTAssertFalse(try accountInformation.actualView().emailWorkflowLaunched)
54+
XCTAssertNoThrow(try accountInformation.find(ViewType.Button.self).tap())
55+
XCTAssert(try accountInformation.actualView().emailWorkflowLaunched)
6256

6357
waitUntil(Self.workflowTestingData != nil)
6458
let data = Self.workflowTestingData
@@ -79,49 +73,38 @@ final class AccountInformationViewTests: XCTestCase, WorkflowTestingReceiver {
7973
// Complete workflow
8074
(Self.workflowTestingData?.orchestrationResponder as? WorkflowViewModel)?.onFinishPublisher.send(.args("new email"))
8175

82-
wait(for: [
83-
ViewHosting.loadView(try accountInformation.actualView()).inspection.inspect { view in
84-
XCTAssertEqual(try view.actualView().email, "new email")
85-
XCTAssertFalse(try view.actualView().emailWorkflowLaunched)
86-
}
87-
], timeout: TestConstant.timeout)
76+
let view = try await accountInformation.actualView().hostAndInspect(with: \.inspection)
77+
XCTAssertEqual(try view.actualView().email, "new email")
78+
XCTAssertFalse(try view.actualView().emailWorkflowLaunched)
8879
}
8980

90-
func testAccountInformationDoesNotBlowUp_IfUsernameWorkflowReturnsSomethingWEIRD() throws {
81+
func testAccountInformationDoesNotBlowUp_IfUsernameWorkflowReturnsSomethingWEIRD() async throws {
9182
class CustomObj { }
9283
Self.workflowLaunchedData.removeAll()
93-
var accountInformation: InspectableView<ViewType.View<AccountInformationView>>!
94-
var expectedEmail = "starting value"
95-
let exp = ViewHosting.loadView(AccountInformationView()).inspection.inspect { view in
96-
accountInformation = view
97-
expectedEmail = try view.actualView().email
98-
XCTAssertNoThrow(try view.find(ViewType.Button.self).tap())
99-
}
100-
wait(for: [exp], timeout: TestConstant.timeout)
10184

102-
if Self.workflowTestingData == nil { throw XCTSkip("test data was not created") }
85+
let accountInformation = try await AccountInformationView().hostAndInspect(with: \.inspection)
86+
let expectedEmail = try accountInformation.actualView().email
87+
88+
XCTAssertNoThrow(try accountInformation.find(ViewType.Button.self).tap())
89+
90+
waitUntil(Self.workflowTestingData != nil)
91+
92+
XCTAssertNotNil(Self.workflowTestingData)
10393
(Self.workflowTestingData?.orchestrationResponder as? WorkflowViewModel)?.onFinishPublisher.send(.args(CustomObj()))
10494

105-
wait(for: [
106-
ViewHosting.loadView(try accountInformation.actualView()).inspection.inspect { view in
107-
XCTAssert(try view.actualView().emailWorkflowLaunched)
108-
XCTAssertEqual(try view.actualView().email, expectedEmail)
109-
}
110-
].compactMap { $0 }, timeout: TestConstant.timeout)
95+
let view = try await accountInformation.actualView().hostAndInspect(with: \.inspection)
96+
XCTAssert(try view.actualView().emailWorkflowLaunched)
97+
XCTAssertEqual(try view.actualView().email, expectedEmail)
11198
}
11299

113-
func testAccountInformationCanLaunchPasswordWorkflowAgnostic() throws {
100+
func testAccountInformationCanLaunchPasswordWorkflowAgnostic() async throws {
114101
Self.workflowLaunchedData.removeAll()
115-
var accountInformation: InspectableView<ViewType.View<AccountInformationView>>!
116-
let exp = ViewHosting.loadView(AccountInformationView()).inspection.inspect { view in
117-
accountInformation = view
118-
XCTAssertFalse(try view.actualView().passwordWorkflowLaunched)
119-
XCTAssertNoThrow(try view.find(ViewType.Button.self, skipFound: 1).tap())
120-
XCTAssert(try view.actualView().passwordWorkflowLaunched)
121-
}
122-
wait(for: [exp], timeout: TestConstant.timeout)
123102

124-
XCTAssertNotNil(accountInformation)
103+
let accountInformation = try await AccountInformationView().hostAndInspect(with: \.inspection)
104+
105+
XCTAssertFalse(try accountInformation.actualView().passwordWorkflowLaunched)
106+
XCTAssertNoThrow(try accountInformation.find(ViewType.Button.self, skipFound: 1).tap())
107+
XCTAssert(try accountInformation.actualView().passwordWorkflowLaunched)
125108

126109
waitUntil(Self.workflowTestingData != nil)
127110
let data = Self.workflowTestingData
@@ -142,48 +125,39 @@ final class AccountInformationViewTests: XCTestCase, WorkflowTestingReceiver {
142125
// Complete workflow
143126
(Self.workflowTestingData?.orchestrationResponder as? WorkflowViewModel)?.onFinishPublisher.send(.args("newPassword"))
144127

145-
wait(for: [
146-
ViewHosting.loadView(try accountInformation.actualView()).inspection.inspect { view in
147-
XCTAssertEqual(try view.actualView().password, "newPassword")
148-
XCTAssertFalse(try view.actualView().passwordWorkflowLaunched)
149-
}
150-
], timeout: TestConstant.timeout)
128+
let view = try await accountInformation.actualView().hostAndInspect(with: \.inspection)
129+
XCTAssertEqual(try view.actualView().password, "newPassword")
130+
XCTAssertFalse(try view.actualView().passwordWorkflowLaunched)
151131
}
152132

153-
func testAccountInformationDoesNotBlowUp_IfPasswordWorkflowReturnsSomethingWEIRD() throws {
133+
func testAccountInformationDoesNotBlowUp_IfPasswordWorkflowReturnsSomethingWEIRD() async throws {
154134
class CustomObj { }
155135
Self.workflowLaunchedData.removeAll()
156-
var accountInformation: InspectableView<ViewType.View<AccountInformationView>>!
157-
var expectedPassword = "starting value"
158-
let exp = ViewHosting.loadView(AccountInformationView()).inspection.inspect { view in
159-
accountInformation = view
160-
expectedPassword = try view.actualView().password
161-
XCTAssertNoThrow(try view.find(ViewType.Button.self, skipFound: 1).tap())
162-
}
163-
wait(for: [exp], timeout: TestConstant.timeout)
164136

165-
if Self.workflowTestingData == nil { throw XCTSkip("test data was not created") }
137+
let accountInformation = try await AccountInformationView().hostAndInspect(with: \.inspection)
138+
let expectedPassword = try accountInformation.actualView().password
139+
XCTAssertNoThrow(try accountInformation.find(ViewType.Button.self, skipFound: 1).tap())
140+
141+
waitUntil(Self.workflowTestingData != nil)
142+
XCTAssertNotNil(Self.workflowTestingData)
143+
166144
(Self.workflowTestingData?.orchestrationResponder as? WorkflowViewModel)?.onFinishPublisher.send(.args(CustomObj()))
167145

168-
wait(for: [
169-
ViewHosting.loadView(try accountInformation.actualView()).inspection.inspect { view in
170-
XCTAssert(try view.actualView().passwordWorkflowLaunched)
171-
XCTAssertEqual(try view.actualView().password, expectedPassword)
172-
}
173-
].compactMap { $0 }, timeout: TestConstant.timeout)
146+
let view = try await accountInformation.actualView().hostAndInspect(with: \.inspection)
147+
XCTAssert(try view.actualView().passwordWorkflowLaunched)
148+
XCTAssertEqual(try view.actualView().password, expectedPassword)
174149
}
175150

176-
func testAccountInformationCanLaunchBothWorkflows() throws {
177-
let exp = ViewHosting.loadView(AccountInformationView()).inspection.inspect { view in
178-
XCTAssertEqual(view.findAll(MFAViewWorkflowView.self).count, 0)
151+
func testAccountInformationCanLaunchBothWorkflows() async throws {
152+
let view = try await AccountInformationView().hostAndInspect(with: \.inspection)
179153

180-
let firstButton = try view.find(ViewType.Button.self)
181-
let secondButton = try view.find(ViewType.Button.self, skipFound: 1)
182-
XCTAssertNoThrow(try secondButton.tap())
183-
XCTAssertNoThrow(try firstButton.tap())
154+
XCTAssertEqual(view.findAll(MFAViewWorkflowView.self).count, 0)
184155

185-
XCTAssertEqual(view.findAll(MFAViewWorkflowView.self).count, 2)
186-
}
187-
wait(for: [exp], timeout: TestConstant.timeout)
156+
let firstButton = try view.find(ViewType.Button.self)
157+
let secondButton = try view.find(ViewType.Button.self, skipFound: 1)
158+
XCTAssertNoThrow(try secondButton.tap())
159+
XCTAssertNoThrow(try firstButton.tap())
160+
161+
XCTAssertEqual(view.findAll(MFAViewWorkflowView.self).count, 2)
188162
}
189163
}

ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ChangePasswordViewTests.swift

Lines changed: 65 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -14,80 +14,97 @@ import SwiftUI
1414
@testable import SwiftUIExample
1515

1616
final class ChangePasswordViewTests: XCTestCase, View {
17-
func testChangePasswordView() throws {
17+
func testChangePasswordView() async throws {
1818
let currentPassword = UUID().uuidString
19-
let exp = ViewHosting.loadView(ChangePasswordView(with: currentPassword)).inspection.inspect { view in
20-
XCTAssertEqual(view.findAll(PasswordField.self).count, 3)
21-
XCTAssertNoThrow(try view.find(ViewType.Button.self))
19+
let view = try await MainActor.run {
20+
ChangePasswordView(with: currentPassword)
2221
}
23-
wait(for: [exp], timeout: TestConstant.timeout)
22+
.hostAndInspect(with: \.inspection)
23+
24+
XCTAssertEqual(view.findAll(PasswordField.self).count, 3)
25+
XCTAssertNoThrow(try view.find(ViewType.Button.self))
2426
}
2527

26-
func testChangePasswordProceeds_IfAllInformationIsCorrect() throws {
28+
func testChangePasswordProceeds_IfAllInformationIsCorrect() async throws {
2729
let currentPassword = UUID().uuidString
2830
let onFinish = expectation(description: "onFinish called")
29-
let exp = ViewHosting.loadView(WorkflowLauncher(isLaunched: .constant(true), startingArgs: currentPassword) {
30-
thenProceed(with: ChangePasswordView.self)
31-
}
32-
.onFinish { _ in onFinish.fulfill() }).inspection.inspect { view in
33-
XCTAssertNoThrow(try view.find(ViewType.SecureField.self).setInput(currentPassword))
34-
XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 1).setInput("asdfF1"))
35-
XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 2).setInput("asdfF1"))
36-
XCTAssertNoThrow(try view.find(ViewType.Button.self).tap())
31+
let view = try await MainActor.run {
32+
WorkflowLauncher(isLaunched: .constant(true), startingArgs: currentPassword) {
33+
thenProceed(with: ChangePasswordView.self)
34+
}
35+
.onFinish { _ in onFinish.fulfill() }
3736
}
38-
wait(for: [exp, onFinish], timeout: TestConstant.timeout)
37+
.hostAndInspect(with: \.inspection)
38+
.extractWorkflowItem()
39+
40+
XCTAssertNoThrow(try view.find(ViewType.SecureField.self).setInput(currentPassword))
41+
XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 1).setInput("asdfF1"))
42+
XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 2).setInput("asdfF1"))
43+
XCTAssertNoThrow(try view.find(ViewType.Button.self).tap())
44+
45+
wait(for: [onFinish], timeout: TestConstant.timeout)
3946
}
4047

41-
func testErrorsDoNotShowUp_IfFormWasNotSubmitted() throws {
48+
func testErrorsDoNotShowUp_IfFormWasNotSubmitted() async throws {
4249
let currentPassword = UUID().uuidString
43-
let exp = ViewHosting.loadView(ChangePasswordView(with: currentPassword)).inspection.inspect { view in
44-
XCTAssertNoThrow(try view.find(ViewType.SecureField.self).setInput(currentPassword))
45-
XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 1).setInput("asdfF1"))
46-
XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 2).setInput("asdfF1"))
47-
XCTAssertNoThrow(try view.find(ViewType.Button.self))
50+
let view = try await MainActor.run {
51+
ChangePasswordView(with: currentPassword)
4852
}
49-
wait(for: [exp], timeout: TestConstant.timeout)
53+
.hostAndInspect(with: \.inspection)
54+
55+
XCTAssertNoThrow(try view.find(ViewType.SecureField.self).setInput(currentPassword))
56+
XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 1).setInput("asdfF1"))
57+
XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 2).setInput("asdfF1"))
58+
XCTAssertNoThrow(try view.find(ViewType.Button.self))
5059
}
5160

52-
func testIncorrectOldPassword_PrintsError() throws {
61+
func testIncorrectOldPassword_PrintsError() async throws {
5362
let currentPassword = UUID().uuidString
54-
let exp = ViewHosting.loadView(ChangePasswordView(with: currentPassword)).inspection.inspect { view in
55-
XCTAssertNoThrow(try view.find(ViewType.SecureField.self).setInput("WRONG"))
56-
XCTAssertNoThrow(try view.find(ViewType.Button.self).tap())
57-
XCTAssert(try view.vStack().text(0).string().contains("Old password does not match records"))
63+
let view = try await MainActor.run {
64+
ChangePasswordView(with: currentPassword)
5865
}
59-
wait(for: [exp], timeout: TestConstant.timeout)
66+
.hostAndInspect(with: \.inspection)
67+
68+
XCTAssertNoThrow(try view.find(ViewType.SecureField.self).setInput("WRONG"))
69+
XCTAssertNoThrow(try view.find(ViewType.Button.self).tap())
70+
XCTAssert(try view.vStack().text(0).string().contains("Old password does not match records"))
6071
}
6172

62-
func testPasswordsNotMatching_PrintsError() throws {
73+
func testPasswordsNotMatching_PrintsError() async throws {
6374
let currentPassword = UUID().uuidString
64-
let exp = ViewHosting.loadView(ChangePasswordView(with: currentPassword)).inspection.inspect { view in
65-
XCTAssertNoThrow(try view.find(ViewType.SecureField.self).setInput(currentPassword))
66-
XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 1).setInput(UUID().uuidString))
67-
XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 2).setInput(UUID().uuidString))
68-
XCTAssertNoThrow(try view.find(ViewType.Button.self).tap())
69-
XCTAssert(try view.vStack().text(0).string().contains("New password and confirmation password do not match"))
75+
let view = try await MainActor.run {
76+
ChangePasswordView(with: currentPassword)
7077
}
71-
wait(for: [exp], timeout: TestConstant.timeout)
78+
.hostAndInspect(with: \.inspection)
79+
80+
XCTAssertNoThrow(try view.find(ViewType.SecureField.self).setInput(currentPassword))
81+
XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 1).setInput(UUID().uuidString))
82+
XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 2).setInput(UUID().uuidString))
83+
XCTAssertNoThrow(try view.find(ViewType.Button.self).tap())
84+
XCTAssert(try view.vStack().text(0).string().contains("New password and confirmation password do not match"))
7285
}
7386

74-
func testPasswordsNotHavingUppercase_PrintsError() throws {
87+
func testPasswordsNotHavingUppercase_PrintsError() async throws {
7588
let currentPassword = UUID().uuidString
76-
let exp = ViewHosting.loadView(ChangePasswordView(with: currentPassword)).inspection.inspect { view in
77-
XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 1).setInput("asdf1"))
78-
XCTAssertNoThrow(try view.find(ViewType.Button.self).tap())
79-
XCTAssert(try view.vStack().text(0).string().contains("Password must contain at least one uppercase character"))
89+
let view = try await MainActor.run {
90+
ChangePasswordView(with: currentPassword)
8091
}
81-
wait(for: [exp], timeout: TestConstant.timeout)
92+
.hostAndInspect(with: \.inspection)
93+
94+
XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 1).setInput("asdf1"))
95+
XCTAssertNoThrow(try view.find(ViewType.Button.self).tap())
96+
XCTAssert(try view.vStack().text(0).string().contains("Password must contain at least one uppercase character"))
8297
}
8398

84-
func testPasswordsNotHavingNumber_PrintsError() throws {
99+
func testPasswordsNotHavingNumber_PrintsError() async throws {
85100
let currentPassword = UUID().uuidString
86-
let exp = ViewHosting.loadView(ChangePasswordView(with: currentPassword)).inspection.inspect { view in
87-
XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 1).setInput("asdfF"))
88-
XCTAssertNoThrow(try view.find(ViewType.Button.self).tap())
89-
XCTAssert(try view.vStack().text(0).string().contains("Password must contain at least one number"))
101+
let view = try await MainActor.run {
102+
ChangePasswordView(with: currentPassword)
90103
}
91-
wait(for: [exp], timeout: TestConstant.timeout)
104+
.hostAndInspect(with: \.inspection)
105+
106+
XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 1).setInput("asdfF"))
107+
XCTAssertNoThrow(try view.find(ViewType.Button.self).tap())
108+
XCTAssert(try view.vStack().text(0).string().contains("Password must contain at least one number"))
92109
}
93110
}

0 commit comments

Comments
 (0)