Skip to content

Commit 99f03e7

Browse files
authored
Merge pull request #8024 from woocommerce/td/7891-AccountCreationFormViewModel-tests
Add unit tests for `AccountCreationFormViewModel`
2 parents 9572042 + 5c3b6ff commit 99f03e7

File tree

3 files changed

+214
-3
lines changed

3 files changed

+214
-3
lines changed

WooCommerce/Classes/ViewModels/Authentication/AccountCreationFormViewModel.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,22 @@ final class AccountCreationFormViewModel: ObservableObject {
2828
private let analytics: Analytics
2929
private var subscriptions: Set<AnyCancellable> = []
3030

31-
init(stores: StoresManager = ServiceLocator.stores,
31+
init(debounceDuration: Double = Constants.fieldDebounceDuration,
32+
stores: StoresManager = ServiceLocator.stores,
3233
analytics: Analytics = ServiceLocator.analytics) {
3334
self.stores = stores
3435
self.analytics = analytics
3536

3637
$email
3738
.removeDuplicates()
38-
.debounce(for: .seconds(Constants.fieldDebounceDuration), scheduler: DispatchQueue.main)
39+
.debounce(for: .seconds(debounceDuration), scheduler: DispatchQueue.main)
3940
.sink { [weak self] email in
4041
self?.validateEmail(email)
4142
}.store(in: &subscriptions)
4243

4344
$password
4445
.removeDuplicates()
45-
.debounce(for: .seconds(Constants.fieldDebounceDuration), scheduler: DispatchQueue.main)
46+
.debounce(for: .seconds(debounceDuration), scheduler: DispatchQueue.main)
4647
.sink { [weak self] password in
4748
self?.validatePassword(password)
4849
}.store(in: &subscriptions)

WooCommerce/WooCommerce.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@
237237
026D4A24280461960090164F /* CollectOrderPaymentUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 026D4A23280461960090164F /* CollectOrderPaymentUseCaseTests.swift */; };
238238
0270F47624D005B00005210A /* ProductFormViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0270F47524D005B00005210A /* ProductFormViewModelProtocol.swift */; };
239239
0270F47824D006F60005210A /* ProductFormPresentationStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0270F47724D006F60005210A /* ProductFormPresentationStyle.swift */; };
240+
027111422913B9FC00F5269A /* AccountCreationFormViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027111412913B9FC00F5269A /* AccountCreationFormViewModelTests.swift */; };
240241
0271125D2887D4E900FCD13C /* LoggedOutAppSettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0271125C2887D4E900FCD13C /* LoggedOutAppSettingsTests.swift */; };
241242
0271139A24DD15D800574A07 /* ProductsTabProductViewModel+VariationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0271139924DD15D800574A07 /* ProductsTabProductViewModel+VariationTests.swift */; };
242243
0271E1642509C66200633F7A /* DefaultProductFormTableViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0271E1632509C66200633F7A /* DefaultProductFormTableViewModelTests.swift */; };
@@ -2189,6 +2190,7 @@
21892190
0270C0A827069BEF00FC799F /* Experiments.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Experiments.framework; sourceTree = BUILT_PRODUCTS_DIR; };
21902191
0270F47524D005B00005210A /* ProductFormViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductFormViewModelProtocol.swift; sourceTree = "<group>"; };
21912192
0270F47724D006F60005210A /* ProductFormPresentationStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductFormPresentationStyle.swift; sourceTree = "<group>"; };
2193+
027111412913B9FC00F5269A /* AccountCreationFormViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountCreationFormViewModelTests.swift; sourceTree = "<group>"; };
21922194
0271125C2887D4E900FCD13C /* LoggedOutAppSettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggedOutAppSettingsTests.swift; sourceTree = "<group>"; };
21932195
0271139924DD15D800574A07 /* ProductsTabProductViewModel+VariationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProductsTabProductViewModel+VariationTests.swift"; sourceTree = "<group>"; };
21942196
0271E1632509C66200633F7A /* DefaultProductFormTableViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultProductFormTableViewModelTests.swift; sourceTree = "<group>"; };
@@ -4486,6 +4488,14 @@
44864488
path = Variations;
44874489
sourceTree = "<group>";
44884490
};
4491+
027111402913B9D400F5269A /* Authentication */ = {
4492+
isa = PBXGroup;
4493+
children = (
4494+
027111412913B9FC00F5269A /* AccountCreationFormViewModelTests.swift */,
4495+
);
4496+
path = Authentication;
4497+
sourceTree = "<group>";
4498+
};
44894499
02759B8F28FFA06F00918176 /* Store Creation */ = {
44904500
isa = PBXGroup;
44914501
children = (
@@ -6094,6 +6104,7 @@
60946104
5791FB4024EC833200117FD6 /* ViewModels */ = {
60956105
isa = PBXGroup;
60966106
children = (
6107+
027111402913B9D400F5269A /* Authentication */,
60976108
D41C9F2F26D9A41F00993558 /* WhatsNew */,
60986109
D8025469265517F9001B2CC1 /* CardPresentPayments */,
60996110
0371C36F2876ED3A00277E2C /* Feature Announcement Cards */,
@@ -11031,6 +11042,7 @@
1103111042
4524CDA1242D045C00B2F20A /* ProductStatusSettingListSelectorCommandTests.swift in Sources */,
1103211043
02077F72253816FF005A78EF /* ProductFormActionsFactory+ReadonlyProductTests.swift in Sources */,
1103311044
D8C11A6222E24C4A00D4A88D /* LedgerTableViewCellTests.swift in Sources */,
11045+
027111422913B9FC00F5269A /* AccountCreationFormViewModelTests.swift in Sources */,
1103411046
DE50295328BF4A8A00551736 /* JetpackConnectionWebViewModelTests.swift in Sources */,
1103511047
AEA622B727468790002A9B57 /* AddOrderCoordinatorTests.swift in Sources */,
1103611048
D802549126552FE1001B2CC1 /* CardPresentModalScanningForReaderTests.swift in Sources */,
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import XCTest
2+
import Yosemite
3+
@testable import WooCommerce
4+
5+
final class AccountCreationFormViewModelTests: XCTestCase {
6+
private var stores: MockStoresManager!
7+
private var analyticsProvider: MockAnalyticsProvider!
8+
private var analytics: WooAnalytics!
9+
private var viewModel: AccountCreationFormViewModel!
10+
11+
override func setUp() {
12+
super.setUp()
13+
stores = MockStoresManager(sessionManager: SessionManager.makeForTesting())
14+
analyticsProvider = MockAnalyticsProvider()
15+
analytics = WooAnalytics(analyticsProvider: analyticsProvider)
16+
viewModel = .init(debounceDuration: 0, stores: stores, analytics: analytics)
17+
}
18+
19+
override func tearDown() {
20+
viewModel = nil
21+
analytics = nil
22+
analyticsProvider = nil
23+
stores = nil
24+
super.tearDown()
25+
}
26+
27+
// MARK: - `isEmailValid`
28+
29+
func test_isEmailValid_is_false_after_entering_invalid_email() {
30+
// When
31+
viewModel.email = "notanemail@woocom"
32+
33+
// Then
34+
waitUntil {
35+
self.viewModel.isEmailValid == false
36+
}
37+
}
38+
39+
func test_isEmailValid_is_true_after_entering_valid_email() {
40+
// When
41+
viewModel.email = "[email protected]"
42+
43+
// Then
44+
waitUntil {
45+
self.viewModel.isEmailValid == true
46+
}
47+
}
48+
49+
// MARK: - `isPasswordValid`
50+
51+
func test_isPasswordValid_is_false_after_entering_password_less_than_minimum_length() {
52+
// When
53+
viewModel.password = "minim"
54+
55+
// Then
56+
waitUntil {
57+
self.viewModel.isPasswordValid == false
58+
}
59+
}
60+
61+
func test_isPasswordValid_is_true_after_entering_password_of_minimum_length() {
62+
// When
63+
viewModel.password = "minimu"
64+
65+
// Then
66+
waitUntil {
67+
self.viewModel.isPasswordValid == true
68+
}
69+
}
70+
71+
// MARK: - `createAccount`
72+
73+
func test_createAccount_success_sets_state_to_authenticated() async {
74+
// Given
75+
mockAccountCreationSuccess(result: .init(authToken: "token", username: "username"))
76+
XCTAssertFalse(stores.isAuthenticated)
77+
78+
// When
79+
let result = await viewModel.createAccount()
80+
81+
// Then
82+
XCTAssertTrue(result.isSuccess)
83+
XCTAssertTrue(stores.isAuthenticated)
84+
}
85+
86+
func test_createAccount_password_failure_sets_passwordErrorMessage() async {
87+
// Given
88+
mockAccountCreationFailure(error: .invalidPassword(message: "too complex to guess"))
89+
XCTAssertFalse(stores.isAuthenticated)
90+
XCTAssertNil(viewModel.passwordErrorMessage)
91+
92+
// When
93+
let result = await viewModel.createAccount()
94+
95+
// Then
96+
XCTAssertTrue(result.isFailure)
97+
XCTAssertFalse(stores.isAuthenticated)
98+
XCTAssertEqual(viewModel.passwordErrorMessage, "too complex to guess")
99+
}
100+
101+
func test_createAccount_invalidEmail_failure_sets_emailErrorMessage() async {
102+
// Given
103+
mockAccountCreationFailure(error: .invalidEmail)
104+
XCTAssertNil(viewModel.emailErrorMessage)
105+
106+
// When
107+
let result = await viewModel.createAccount()
108+
109+
// Then
110+
XCTAssertTrue(result.isFailure)
111+
XCTAssertNotNil(viewModel.emailErrorMessage)
112+
}
113+
114+
func test_passwordErrorMessage_is_cleared_after_changing_password_input() async {
115+
// Given
116+
mockAccountCreationFailure(error: .invalidPassword(message: "too complex to guess"))
117+
118+
// When
119+
let _ = await viewModel.createAccount()
120+
viewModel.password = "simple password"
121+
122+
// Then
123+
waitUntil {
124+
self.viewModel.passwordErrorMessage == nil
125+
}
126+
}
127+
128+
func test_emailErrorMessage_is_cleared_after_changing_email_input() async {
129+
// Given
130+
mockAccountCreationFailure(error: .emailExists)
131+
132+
// When
133+
let _ = await viewModel.createAccount()
134+
viewModel.email = "[email protected]"
135+
136+
// Then
137+
waitUntil {
138+
self.viewModel.emailErrorMessage == nil
139+
}
140+
}
141+
142+
// MARK: - analytics
143+
144+
func test_createAccount_success_tracks_expected_events() async {
145+
// Given
146+
mockAccountCreationSuccess(result: .init(authToken: "", username: ""))
147+
148+
// When
149+
let _ = await viewModel.createAccount()
150+
151+
// Then
152+
XCTAssertEqual(analyticsProvider.receivedEvents, ["signup_submitted", "signup_success"])
153+
}
154+
155+
func test_createAccount_failure_tracks_expected_events() async {
156+
// Given
157+
mockAccountCreationFailure(error: .emailExists)
158+
159+
// When
160+
let _ = await viewModel.createAccount()
161+
162+
// Then
163+
XCTAssertEqual(analyticsProvider.receivedEvents, ["signup_submitted", "signup_failed"])
164+
}
165+
}
166+
167+
private extension AccountCreationFormViewModelTests {
168+
func mockAccountCreationSuccess(result: CreateAccountResult) {
169+
stores.whenReceivingAction(ofType: AccountCreationAction.self) { action in
170+
switch action {
171+
case let .createAccount(_, _, completion):
172+
completion(.success(result))
173+
}
174+
}
175+
176+
stores.whenReceivingAction(ofType: AccountAction.self) { action in
177+
switch action {
178+
case let .synchronizeAccount(completion):
179+
completion(.success(.fake()))
180+
case let .synchronizeAccountSettings(_, completion):
181+
completion(.success(.fake()))
182+
case let .synchronizeSites(_, completion):
183+
completion(.success(true))
184+
default:
185+
break
186+
}
187+
}
188+
}
189+
190+
func mockAccountCreationFailure(error: CreateAccountError) {
191+
stores.whenReceivingAction(ofType: AccountCreationAction.self) { action in
192+
guard case let .createAccount(_, _, completion) = action else {
193+
return
194+
}
195+
completion(.failure(error))
196+
}
197+
}
198+
}

0 commit comments

Comments
 (0)