From c67c4e362ebc4856162f0934acd78445401e17f8 Mon Sep 17 00:00:00 2001 From: Mauro <34335419+Velin92@users.noreply.github.com> Date: Wed, 8 Mar 2023 17:04:31 +0100 Subject: [PATCH] Auto-Mocking with Sourcery (#597) * work in progress, was able to generate a mock for the RoomProxyProtocol, I'll try if I can swap the mock we have with this one * removing swiftformat from generated * added the disable of swiftlint directly into the .stencil * testing if danger still complains * improved the stencil * session verification controller proxy using auto mockable * BugReport mocks and tests added * changelog * fixing a typo Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com> * fix typo in the test Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com> * removing the Protocol word from the type if present * using extension in place of a subclass * removed unused imports * improved the yielding code * moved Sourcery files * stencil master --------- Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com> --- .swiftformat | 2 +- .../AutoMockable.swift} | 20 +- .../Mocks/Generated/GeneratedMocks.swift | 143 +++++++++++++ ...ssionVerificationControllerProxyMock.swift | 87 ++++++++ .../BugReport/View/BugReportScreen.swift | 6 +- .../View/SessionVerificationScreen.swift | 4 +- .../BugReport/BugReportServiceProtocol.swift | 4 +- ...ckSessionVerificationControllerProxy.swift | 90 --------- ...nVerificationControllerProxyProtocol.swift | 2 +- .../UITests/UITestsAppCoordinator.swift | 13 +- ElementX/SupportingFiles/target.yml | 11 + Tools/Sourcery/AutoMockable.stencil | 190 ++++++++++++++++++ .../Sourcery/sourcery_automockable_config.yml | 9 + Tools/Sources/SetupProject.swift | 2 +- UnitTests/Sources/BugReportServiceTests.swift | 8 +- .../Sources/BugReportViewModelTests.swift | 60 +++++- ...SessionVerificationStateMachineTests.swift | 16 +- .../SessionVerificationViewModelTests.swift | 22 +- .../UserSession/UserSessionTests.swift | 12 +- changelog.d/600.feature | 1 + 20 files changed, 554 insertions(+), 148 deletions(-) rename ElementX/Sources/{Services/BugReport/MockBugReportService.swift => Mocks/AutoMockable.swift} (56%) create mode 100644 ElementX/Sources/Mocks/Generated/GeneratedMocks.swift create mode 100644 ElementX/Sources/Mocks/SessionVerificationControllerProxyMock.swift delete mode 100644 ElementX/Sources/Services/SessionVerification/MockSessionVerificationControllerProxy.swift create mode 100644 Tools/Sourcery/AutoMockable.stencil create mode 100644 Tools/Sourcery/sourcery_automockable_config.yml create mode 100644 changelog.d/600.feature diff --git a/.swiftformat b/.swiftformat index aaf4e3e7f..2ad2ac657 100644 --- a/.swiftformat +++ b/.swiftformat @@ -1,6 +1,6 @@ --swiftversion 5.6 ---exclude ElementX/Sources/Generated,vendor,**/Package.swift +--exclude ElementX/Sources/Generated,vendor,**/Package.swift,ElementX/Sources/Mocks/Generated --disable wrapMultiLineStatementBraces --disable hoistPatternLet diff --git a/ElementX/Sources/Services/BugReport/MockBugReportService.swift b/ElementX/Sources/Mocks/AutoMockable.swift similarity index 56% rename from ElementX/Sources/Services/BugReport/MockBugReportService.swift rename to ElementX/Sources/Mocks/AutoMockable.swift index 2df469174..d72a54aeb 100644 --- a/ElementX/Sources/Services/BugReport/MockBugReportService.swift +++ b/ElementX/Sources/Mocks/AutoMockable.swift @@ -1,5 +1,5 @@ // -// Copyright 2022 New Vector Ltd +// Copyright 2023 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,18 +14,6 @@ // limitations under the License. // -import Foundation -import UIKit - -class MockBugReportService: BugReportServiceProtocol { - func submitBugReport(_ bugReport: BugReport, - progressListener: ProgressListener?) async throws -> SubmitBugReportResponse { - SubmitBugReportResponse(reportUrl: "https://www.example/com/123") - } - - var crashedLastRun = false - - func crash() { - // no-op - } -} +// This protocol is used only as a marker +// to mark protocols that can be auto-mocked by Sourcery +protocol AutoMockable { } diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift new file mode 100644 index 000000000..22d6a2b29 --- /dev/null +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -0,0 +1,143 @@ +// Generated using Sourcery 2.0.1 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT + +// swiftlint:disable all + +import Combine +import MatrixRustSDK +class BugReportServiceMock: BugReportServiceProtocol { + var crashedLastRun: Bool { + get { return underlyingCrashedLastRun } + set(value) { underlyingCrashedLastRun = value } + } + var underlyingCrashedLastRun: Bool! + // MARK: - crash + + var crashCallsCount = 0 + var crashCalled: Bool { + return crashCallsCount > 0 + } + var crashClosure: (() -> Void)? + func crash() { + crashCallsCount += 1 + crashClosure?() + } + // MARK: - submitBugReport + + var submitBugReportProgressListenerThrowableError: Error? + var submitBugReportProgressListenerCallsCount = 0 + var submitBugReportProgressListenerCalled: Bool { + return submitBugReportProgressListenerCallsCount > 0 + } + var submitBugReportProgressListenerReceivedArguments: (bugReport: BugReport, progressListener: ProgressListener?)? + var submitBugReportProgressListenerReceivedInvocations: [(bugReport: BugReport, progressListener: ProgressListener?)] = [] + var submitBugReportProgressListenerReturnValue: SubmitBugReportResponse! + var submitBugReportProgressListenerClosure: ((BugReport, ProgressListener?) async throws -> SubmitBugReportResponse)? + func submitBugReport(_ bugReport: BugReport, progressListener: ProgressListener?) async throws -> SubmitBugReportResponse { + if let error = submitBugReportProgressListenerThrowableError { + throw error + } + submitBugReportProgressListenerCallsCount += 1 + submitBugReportProgressListenerReceivedArguments = (bugReport: bugReport, progressListener: progressListener) + submitBugReportProgressListenerReceivedInvocations.append((bugReport: bugReport, progressListener: progressListener)) + if let submitBugReportProgressListenerClosure = submitBugReportProgressListenerClosure { + return try await submitBugReportProgressListenerClosure(bugReport, progressListener) + } else { + return submitBugReportProgressListenerReturnValue + } + } +} +class SessionVerificationControllerProxyMock: SessionVerificationControllerProxyProtocol { + var callbacks: PassthroughSubject { + get { return underlyingCallbacks } + set(value) { underlyingCallbacks = value } + } + var underlyingCallbacks: PassthroughSubject! + var isVerified: Bool { + get { return underlyingIsVerified } + set(value) { underlyingIsVerified = value } + } + var underlyingIsVerified: Bool! + // MARK: - requestVerification + + var requestVerificationCallsCount = 0 + var requestVerificationCalled: Bool { + return requestVerificationCallsCount > 0 + } + var requestVerificationReturnValue: Result! + var requestVerificationClosure: (() async -> Result)? + func requestVerification() async -> Result { + requestVerificationCallsCount += 1 + if let requestVerificationClosure = requestVerificationClosure { + return await requestVerificationClosure() + } else { + return requestVerificationReturnValue + } + } + // MARK: - startSasVerification + + var startSasVerificationCallsCount = 0 + var startSasVerificationCalled: Bool { + return startSasVerificationCallsCount > 0 + } + var startSasVerificationReturnValue: Result! + var startSasVerificationClosure: (() async -> Result)? + func startSasVerification() async -> Result { + startSasVerificationCallsCount += 1 + if let startSasVerificationClosure = startSasVerificationClosure { + return await startSasVerificationClosure() + } else { + return startSasVerificationReturnValue + } + } + // MARK: - approveVerification + + var approveVerificationCallsCount = 0 + var approveVerificationCalled: Bool { + return approveVerificationCallsCount > 0 + } + var approveVerificationReturnValue: Result! + var approveVerificationClosure: (() async -> Result)? + func approveVerification() async -> Result { + approveVerificationCallsCount += 1 + if let approveVerificationClosure = approveVerificationClosure { + return await approveVerificationClosure() + } else { + return approveVerificationReturnValue + } + } + // MARK: - declineVerification + + var declineVerificationCallsCount = 0 + var declineVerificationCalled: Bool { + return declineVerificationCallsCount > 0 + } + var declineVerificationReturnValue: Result! + var declineVerificationClosure: (() async -> Result)? + func declineVerification() async -> Result { + declineVerificationCallsCount += 1 + if let declineVerificationClosure = declineVerificationClosure { + return await declineVerificationClosure() + } else { + return declineVerificationReturnValue + } + } + // MARK: - cancelVerification + + var cancelVerificationCallsCount = 0 + var cancelVerificationCalled: Bool { + return cancelVerificationCallsCount > 0 + } + var cancelVerificationReturnValue: Result! + var cancelVerificationClosure: (() async -> Result)? + func cancelVerification() async -> Result { + cancelVerificationCallsCount += 1 + if let cancelVerificationClosure = cancelVerificationClosure { + return await cancelVerificationClosure() + } else { + return cancelVerificationReturnValue + } + } +} + +// swiftlint:enable all diff --git a/ElementX/Sources/Mocks/SessionVerificationControllerProxyMock.swift b/ElementX/Sources/Mocks/SessionVerificationControllerProxyMock.swift new file mode 100644 index 000000000..16334f508 --- /dev/null +++ b/ElementX/Sources/Mocks/SessionVerificationControllerProxyMock.swift @@ -0,0 +1,87 @@ +// +// Copyright 2023 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine + +extension SessionVerificationControllerProxyMock { + static let emojis = [SessionVerificationEmoji(symbol: "🦋", description: "Butterfly"), + SessionVerificationEmoji(symbol: "🐘", description: "Elephant"), + SessionVerificationEmoji(symbol: "🦋", description: "Butterfly"), + SessionVerificationEmoji(symbol: "🎂", description: "Cake"), + SessionVerificationEmoji(symbol: "🎂", description: "Cake"), + SessionVerificationEmoji(symbol: "🏁", description: "Flag"), + SessionVerificationEmoji(symbol: "🌏", description: "Globe")] + + static func configureMock(callbacks: PassthroughSubject = .init(), + isVerified: Bool = false, + requestDelay: Duration = .seconds(1)) -> SessionVerificationControllerProxyMock { + let mock = SessionVerificationControllerProxyMock() + mock.underlyingCallbacks = callbacks + mock.underlyingIsVerified = isVerified + + mock.requestVerificationClosure = { [unowned mock] in + Task.detached { + try await Task.sleep(for: requestDelay) + mock.callbacks.send(.acceptedVerificationRequest) + } + + return .success(()) + } + + mock.startSasVerificationClosure = { [unowned mock] in + Task.detached { + try await Task.sleep(for: requestDelay) + mock.callbacks.send(.startedSasVerification) + + Task.detached { + try await Task.sleep(for: requestDelay) + mock.callbacks.send(.receivedVerificationData(Self.emojis)) + } + } + + return .success(()) + } + + mock.approveVerificationClosure = { [unowned mock] in + Task.detached { + try await Task.sleep(for: requestDelay) + mock.callbacks.send(.finished) + } + + return .success(()) + } + + mock.declineVerificationClosure = { [unowned mock] in + Task.detached { + try await Task.sleep(for: requestDelay) + mock.callbacks.send(.cancelled) + } + + return .success(()) + } + + mock.cancelVerificationClosure = { [unowned mock] in + Task.detached { + try await Task.sleep(for: requestDelay) + mock.callbacks.send(.cancelled) + } + + return .success(()) + } + + return mock + } +} diff --git a/ElementX/Sources/Screens/BugReport/View/BugReportScreen.swift b/ElementX/Sources/Screens/BugReport/View/BugReportScreen.swift index 67fd13f9e..1f92ecdce 100644 --- a/ElementX/Sources/Screens/BugReport/View/BugReportScreen.swift +++ b/ElementX/Sources/Screens/BugReport/View/BugReportScreen.swift @@ -146,7 +146,7 @@ struct BugReportScreen: View { // MARK: - Previews struct BugReport_Previews: PreviewProvider { - static let viewModel = BugReportViewModel(bugReportService: MockBugReportService(), + static let viewModel = BugReportViewModel(bugReportService: BugReportServiceMock(), userID: "@mock.client.com", deviceID: nil, screenshot: nil, @@ -154,7 +154,7 @@ struct BugReport_Previews: PreviewProvider { static var previews: some View { NavigationStack { - BugReportScreen(context: BugReportViewModel(bugReportService: MockBugReportService(), + BugReportScreen(context: BugReportViewModel(bugReportService: BugReportServiceMock(), userID: "@mock.client.com", deviceID: nil, screenshot: nil, @@ -163,7 +163,7 @@ struct BugReport_Previews: PreviewProvider { } NavigationStack { - BugReportScreen(context: BugReportViewModel(bugReportService: MockBugReportService(), + BugReportScreen(context: BugReportViewModel(bugReportService: BugReportServiceMock(), userID: "@mock.client.com", deviceID: nil, screenshot: Asset.Images.appLogo.image, diff --git a/ElementX/Sources/Screens/SessionVerification/View/SessionVerificationScreen.swift b/ElementX/Sources/Screens/SessionVerification/View/SessionVerificationScreen.swift index fba2f175f..2e33ab687 100644 --- a/ElementX/Sources/Screens/SessionVerification/View/SessionVerificationScreen.swift +++ b/ElementX/Sources/Screens/SessionVerification/View/SessionVerificationScreen.swift @@ -231,14 +231,14 @@ struct SessionVerification_Previews: PreviewProvider { sessionVerificationScreen(state: .requestingVerification) sessionVerificationScreen(state: .cancelled) - sessionVerificationScreen(state: .showingChallenge(emojis: MockSessionVerificationControllerProxy.emojis)) + sessionVerificationScreen(state: .showingChallenge(emojis: SessionVerificationControllerProxyMock.emojis)) sessionVerificationScreen(state: .verified) } .tint(Color.element.accent) } static func sessionVerificationScreen(state: SessionVerificationStateMachine.State) -> some View { - let viewModel = SessionVerificationViewModel(sessionVerificationControllerProxy: MockSessionVerificationControllerProxy(), + let viewModel = SessionVerificationViewModel(sessionVerificationControllerProxy: SessionVerificationControllerProxyMock.configureMock(), initialState: SessionVerificationViewState(verificationState: state)) return SessionVerificationScreen(context: viewModel.context) diff --git a/ElementX/Sources/Services/BugReport/BugReportServiceProtocol.swift b/ElementX/Sources/Services/BugReport/BugReportServiceProtocol.swift index e3ed76e96..de75640ed 100644 --- a/ElementX/Sources/Services/BugReport/BugReportServiceProtocol.swift +++ b/ElementX/Sources/Services/BugReport/BugReportServiceProtocol.swift @@ -17,7 +17,7 @@ import Foundation import UIKit -struct BugReport { +struct BugReport: Equatable { let userID: String let deviceID: String? let text: String @@ -31,7 +31,7 @@ struct SubmitBugReportResponse: Decodable { var reportUrl: String } -protocol BugReportServiceProtocol { +protocol BugReportServiceProtocol: AutoMockable { var crashedLastRun: Bool { get } func crash() diff --git a/ElementX/Sources/Services/SessionVerification/MockSessionVerificationControllerProxy.swift b/ElementX/Sources/Services/SessionVerification/MockSessionVerificationControllerProxy.swift deleted file mode 100644 index 90d37a11c..000000000 --- a/ElementX/Sources/Services/SessionVerification/MockSessionVerificationControllerProxy.swift +++ /dev/null @@ -1,90 +0,0 @@ -// -// Copyright 2022 New Vector Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Combine -import Foundation -import MatrixRustSDK - -struct MockSessionVerificationControllerProxy: SessionVerificationControllerProxyProtocol { - var callbacks = PassthroughSubject() - - var isVerified = false - var requestDelay: Duration = .seconds(1) - - func requestVerification() async -> Result { - Task.detached { - try await Task.sleep(for: requestDelay) - callbacks.send(.acceptedVerificationRequest) - } - - return .success(()) - } - - func startSasVerification() async -> Result { - Task.detached { - try await Task.sleep(for: requestDelay) - callbacks.send(.startedSasVerification) - - Task.detached { - try await Task.sleep(for: requestDelay) - callbacks.send(.receivedVerificationData(Self.emojis)) - } - } - - return .success(()) - } - - func approveVerification() async -> Result { - Task.detached { - try await Task.sleep(for: requestDelay) - callbacks.send(.finished) - } - - return .success(()) - } - - func declineVerification() async -> Result { - Task.detached { - try await Task.sleep(for: requestDelay) - callbacks.send(.cancelled) - } - - return .success(()) - } - - func cancelVerification() async -> Result { - Task.detached { - try await Task.sleep(for: requestDelay) - callbacks.send(.cancelled) - } - - return .success(()) - } - - // MARK: - Private - - static var emojis: [SessionVerificationEmoji] { - [ - SessionVerificationEmoji(symbol: "🦋", description: "Butterfly"), - SessionVerificationEmoji(symbol: "🐘", description: "Elephant"), - SessionVerificationEmoji(symbol: "🦋", description: "Butterfly"), - SessionVerificationEmoji(symbol: "🎂", description: "Cake"), - SessionVerificationEmoji(symbol: "🎂", description: "Cake"), - SessionVerificationEmoji(symbol: "🏁", description: "Flag"), - SessionVerificationEmoji(symbol: "🌏", description: "Globe") - ] - } -} diff --git a/ElementX/Sources/Services/SessionVerification/SessionVerificationControllerProxyProtocol.swift b/ElementX/Sources/Services/SessionVerification/SessionVerificationControllerProxyProtocol.swift index 3cec7c22d..f4353bd92 100644 --- a/ElementX/Sources/Services/SessionVerification/SessionVerificationControllerProxyProtocol.swift +++ b/ElementX/Sources/Services/SessionVerification/SessionVerificationControllerProxyProtocol.swift @@ -39,7 +39,7 @@ struct SessionVerificationEmoji: Hashable { let description: String } -protocol SessionVerificationControllerProxyProtocol { +protocol SessionVerificationControllerProxyProtocol: AutoMockable { var callbacks: PassthroughSubject { get } var isVerified: Bool { get } diff --git a/ElementX/Sources/UITests/UITestsAppCoordinator.swift b/ElementX/Sources/UITests/UITestsAppCoordinator.swift index f46474636..d30417a84 100644 --- a/ElementX/Sources/UITests/UITestsAppCoordinator.swift +++ b/ElementX/Sources/UITests/UITestsAppCoordinator.swift @@ -105,7 +105,7 @@ class MockScreen: Identifiable { mediaProvider: MockMediaProvider()) let coordinator = HomeScreenCoordinator(parameters: .init(userSession: session, attributedStringBuilder: AttributedStringBuilder(), - bugReportService: MockBugReportService(), + bugReportService: BugReportServiceMock(), navigationStackCoordinator: navigationStackCoordinator)) navigationStackCoordinator.setRootCoordinator(coordinator) return navigationStackCoordinator @@ -115,12 +115,12 @@ class MockScreen: Identifiable { userIndicatorController: nil, userSession: MockUserSession(clientProxy: MockClientProxy(userID: "@mock:client.com"), mediaProvider: MockMediaProvider()), - bugReportService: MockBugReportService())) + bugReportService: BugReportServiceMock())) navigationStackCoordinator.setRootCoordinator(coordinator) return navigationStackCoordinator case .bugReport: let navigationStackCoordinator = NavigationStackCoordinator() - let coordinator = BugReportCoordinator(parameters: .init(bugReportService: MockBugReportService(), + let coordinator = BugReportCoordinator(parameters: .init(bugReportService: BugReportServiceMock(), userID: "@mock:client.com", deviceID: nil, userIndicatorController: nil, @@ -130,7 +130,7 @@ class MockScreen: Identifiable { return navigationStackCoordinator case .bugReportWithScreenshot: let navigationStackCoordinator = NavigationStackCoordinator() - let coordinator = BugReportCoordinator(parameters: .init(bugReportService: MockBugReportService(), + let coordinator = BugReportCoordinator(parameters: .init(bugReportService: BugReportServiceMock(), userID: "@mock:client.com", deviceID: nil, userIndicatorController: nil, @@ -250,8 +250,7 @@ class MockScreen: Identifiable { navigationStackCoordinator.setRootCoordinator(coordinator) return navigationStackCoordinator case .sessionVerification: - var sessionVerificationControllerProxy = MockSessionVerificationControllerProxy() - sessionVerificationControllerProxy.requestDelay = .seconds(2) + var sessionVerificationControllerProxy = SessionVerificationControllerProxyMock.configureMock(requestDelay: .seconds(2)) let parameters = SessionVerificationCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationControllerProxy) return SessionVerificationCoordinator(parameters: parameters) case .userSessionScreen: @@ -261,7 +260,7 @@ class MockScreen: Identifiable { let coordinator = UserSessionFlowCoordinator(userSession: MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider()), navigationSplitCoordinator: navigationSplitCoordinator, - bugReportService: MockBugReportService(), + bugReportService: BugReportServiceMock(), roomTimelineControllerFactory: MockRoomTimelineControllerFactory()) coordinator.start() diff --git a/ElementX/SupportingFiles/target.yml b/ElementX/SupportingFiles/target.yml index 6c6359890..5f7c203c6 100644 --- a/ElementX/SupportingFiles/target.yml +++ b/ElementX/SupportingFiles/target.yml @@ -87,6 +87,17 @@ targets: else echo "warning: SwiftGen not installed, download from https://github.com/SwiftGen/SwiftGen" fi + - name: 🧙 Sourcery + runOnlyWhenInstalling: false + basedOnDependencyAnalysis: false + shell: /bin/sh + script: | + export PATH="$PATH:/opt/homebrew/bin" + if which sourcery >/dev/null; then + sourcery --config Tools/Sourcery/sourcery_automockable_config.yml + else + echo "warning: Sourcert not installed, run swift run tools setup-project" + fi postBuildScripts: - name: ⚠️ SwiftLint diff --git a/Tools/Sourcery/AutoMockable.stencil b/Tools/Sourcery/AutoMockable.stencil new file mode 100644 index 000000000..848f26dcb --- /dev/null +++ b/Tools/Sourcery/AutoMockable.stencil @@ -0,0 +1,190 @@ +// swiftlint:disable all + +{% for import in argument.autoMockableImports %} +import {{ import }} +{% endfor %} +{% for import in argument.autoMockableTestableImports %} +@testable import {{ import }} +{% endfor %} +{% macro swiftifyMethodName name %}{{ name | replace:"(","_" | replace:")","" | replace:":","_" | replace:"`","" | snakeToCamelCase | lowerFirstWord }}{% endmacro %} +{% macro accessLevel level %}{% if level != 'internal' %}{{ level }} {% endif %}{% endmacro %} +{% macro staticSpecifier method %}{% if method.isStatic and not method.isInitializer %}static {% endif %}{% endmacro %} +{% macro methodThrowableErrorDeclaration method %} + {% call accessLevel method.accessLevel %}{% call staticSpecifier method %}var {% call swiftifyMethodName method.selectorName %}ThrowableError: Error? +{% endmacro %} +{% macro methodThrowableErrorUsage method %} + if let error = {% call swiftifyMethodName method.selectorName %}ThrowableError { + throw error + } +{% endmacro %} +{% macro methodReceivedParameters method %} + {% set hasNonEscapingClosures %} + {%- for param in method.parameters where param.isClosure and not param.typeAttributes.escaping %} + {{ true }} + {% endfor -%} + {% endset %} + {% if method.parameters.count == 1 and not hasNonEscapingClosures %} + {% call swiftifyMethodName method.selectorName %}Received{% for param in method.parameters %}{{ param.name|upperFirstLetter }} = {{ param.name }}{% endfor %} + {% call swiftifyMethodName method.selectorName %}ReceivedInvocations.append({% for param in method.parameters %}{{ param.name }}){% endfor %} + {% else %} + {% if not method.parameters.count == 0 and not hasNonEscapingClosures %} + {% call swiftifyMethodName method.selectorName %}ReceivedArguments = ({% for param in method.parameters %}{{ param.name }}: {{ param.name }}{% if not forloop.last%}, {% endif %}{% endfor %}) + {% call swiftifyMethodName method.selectorName %}ReceivedInvocations.append(({% for param in method.parameters %}{{ param.name }}: {{ param.name }}{% if not forloop.last%}, {% endif %}{% endfor %})) + {% endif %} + {% endif %} +{% endmacro %} +{% macro methodClosureName method %}{% call swiftifyMethodName method.selectorName %}Closure{% endmacro %} +{% macro closureReturnTypeName method %}{% if method.isOptionalReturnType %}{{ method.unwrappedReturnTypeName }}?{% else %}{{ method.returnTypeName }}{% endif %}{% endmacro %} +{% macro methodClosureDeclaration method %} + {% call accessLevel method.accessLevel %}{% call staticSpecifier method %}var {% call methodClosureName method %}: (({% for param in method.parameters %}{{ param.typeName }}{% if not forloop.last %}, {% endif %}{% endfor %}) {% if method.isAsync %}async {% endif %}{% if method.throws %}throws {% endif %}-> {% if method.isInitializer %}Void{% else %}{% call closureReturnTypeName method %}{% endif %})? +{% endmacro %} +{% macro methodClosureCallParameters method %}{% for param in method.parameters %}{{ param.name }}{% if not forloop.last %}, {% endif %}{% endfor %}{% endmacro %} +{% macro mockMethod method %} + // MARK: - {{ method.shortName }} + + {% if method.throws %} + {% call methodThrowableErrorDeclaration method %} + {% endif %} + {% if not method.isInitializer %} + {% call accessLevel method.accessLevel %}{% call staticSpecifier method %}var {% call swiftifyMethodName method.selectorName %}CallsCount = 0 + {% call accessLevel method.accessLevel %}{% call staticSpecifier method %}var {% call swiftifyMethodName method.selectorName %}Called: Bool { + return {% call swiftifyMethodName method.selectorName %}CallsCount > 0 + } + {% endif %} + {% set hasNonEscapingClosures %} + {%- for param in method.parameters where param.isClosure and not param.typeAttributes.escaping %} + {{ true }} + {% endfor -%} + {% endset %} + {% if method.parameters.count == 1 and not hasNonEscapingClosures %} + {% call accessLevel method.accessLevel %}{% call staticSpecifier method %}var {% call swiftifyMethodName method.selectorName %}Received{% for param in method.parameters %}{{ param.name|upperFirstLetter }}: {{ '(' if param.isClosure }}{{ param.typeName.unwrappedTypeName }}{{ ')' if param.isClosure }}?{% endfor %} + {% call accessLevel method.accessLevel %}{% call staticSpecifier method %}var {% call swiftifyMethodName method.selectorName %}ReceivedInvocations{% for param in method.parameters %}: [{{ '(' if param.isClosure }}{{ param.typeName.unwrappedTypeName }}{{ ')' if param.isClosure }}{%if param.typeName.isOptional%}?{%endif%}]{% endfor %} = [] + {% elif not method.parameters.count == 0 and not hasNonEscapingClosures %} + {% call accessLevel method.accessLevel %}{% call staticSpecifier method %}var {% call swiftifyMethodName method.selectorName %}ReceivedArguments: ({% for param in method.parameters %}{{ param.name }}: {{ param.unwrappedTypeName if param.typeAttributes.escaping else param.typeName }}{{ ', ' if not forloop.last }}{% endfor %})? + {% call accessLevel method.accessLevel %}{% call staticSpecifier method %}var {% call swiftifyMethodName method.selectorName %}ReceivedInvocations: [({% for param in method.parameters %}{{ param.name }}: {{ param.unwrappedTypeName if param.typeAttributes.escaping else param.typeName }}{{ ', ' if not forloop.last }}{% endfor %})] = [] + {% endif %} + {% if not method.returnTypeName.isVoid and not method.isInitializer %} + {% call accessLevel method.accessLevel %}{% call staticSpecifier method %}var {% call swiftifyMethodName method.selectorName %}ReturnValue: {{ '(' if method.returnTypeName.isClosure and not method.isOptionalReturnType }}{{ method.returnTypeName }}{{ ')' if method.returnTypeName.isClosure and not method.isOptionalReturnType }}{{ '!' if not method.isOptionalReturnType }} + {% endif %} + {% call methodClosureDeclaration method %} +{% if method.isInitializer %} + {% call accessLevel method.accessLevel %}required {{ method.name }} { + {% call methodReceivedParameters method %} + {% call methodClosureName method %}?({% call methodClosureCallParameters method %}) + } +{% else %} + {% for name, attribute in method.attributes %} + {% for value in attribute %} + {{ value }} + {% endfor %} + {% endfor %} + {% call accessLevel method.accessLevel %}{% call staticSpecifier method %}func {{ method.name }}{{ ' async' if method.isAsync }}{{ ' throws' if method.throws }}{% if not method.returnTypeName.isVoid %} -> {{ method.returnTypeName }}{% endif %} { + {% if method.throws %} + {% call methodThrowableErrorUsage method %} + {% endif %} + {% call swiftifyMethodName method.selectorName %}CallsCount += 1 + {% call methodReceivedParameters method %} + {% if method.returnTypeName.isVoid %} + {% if method.throws %}try {% endif %}{% if method.isAsync %}await {% endif %}{% call methodClosureName method %}?({% call methodClosureCallParameters method %}) + {% else %} + if let {% call methodClosureName method %} = {% call methodClosureName method %} { + return {{ 'try ' if method.throws }}{{ 'await ' if method.isAsync }}{% call methodClosureName method %}({% call methodClosureCallParameters method %}) + } else { + return {% call swiftifyMethodName method.selectorName %}ReturnValue + } + {% endif %} + } +{% endif %} +{% endmacro %} +{% macro resetMethod method %} + {# for type method which are mocked, a way to reset the invocation, argument, etc #} + {% if method.isStatic and not method.isInitializer %} // MARK: - {{ method.shortName }} + {% if not method.isInitializer %} + {% call swiftifyMethodName method.selectorName %}CallsCount = 0 + {% endif %} + {% if method.parameters.count == 1 %} + {% call swiftifyMethodName method.selectorName %}Received{% for param in method.parameters %}{{ param.name|upperFirstLetter }}{% endfor %} = nil + {% call swiftifyMethodName method.selectorName %}ReceivedInvocations = [] + {% elif not method.parameters.count == 0 %} + {% call swiftifyMethodName method.selectorName %}ReceivedArguments = nil + {% call swiftifyMethodName method.selectorName %}ReceivedInvocations = [] + {% endif %} + {% call methodClosureName method %} = nil + {% if method.throws %} + {% call swiftifyMethodName method.selectorName %}ThrowableError = nil + {% endif %} + + {% endif %} +{% endmacro %} +{% macro mockOptionalVariable variable %} + {% call accessLevel variable.readAccess %}var {% call mockedVariableName variable %}: {{ variable.typeName }} +{% endmacro %} +{% macro mockNonOptionalArrayOrDictionaryVariable variable %} + {% call accessLevel variable.readAccess %}var {% call mockedVariableName variable %}: {{ variable.typeName }} = {% if variable.isArray %}[]{% elif variable.isDictionary %}[:]{% endif %} +{% endmacro %} +{% macro mockNonOptionalVariable variable %} + {% call accessLevel variable.readAccess %}var {% call mockedVariableName variable %}: {{ variable.typeName }} { + get { return {% call underlyingMockedVariableName variable %} } + set(value) { {% call underlyingMockedVariableName variable %} = value } + } + {% call accessLevel variable.readAccess %}var {% call underlyingMockedVariableName variable %}: {{ variable.typeName }}! +{% endmacro %} +{% macro variableThrowableErrorDeclaration variable %} + {% call accessLevel variable.readAccess %}var {% call mockedVariableName variable %}ThrowableError: Error? +{% endmacro %} +{% macro variableThrowableErrorUsage variable %} + if let error = {% call mockedVariableName variable %}ThrowableError { + throw error + } +{% endmacro %} +{% macro variableClosureDeclaration variable %} + {% call accessLevel variable.readAccess %}var {% call variableClosureName variable %}: (() {% if variable.isAsync %}async {% endif %}{% if variable.throws %}throws {% endif %}-> {{ variable.typeName }})? +{% endmacro %} +{% macro variableClosureName variable %}{% call mockedVariableName variable %}Closure{% endmacro %} +{% macro mockAsyncOrThrowingVariable variable %} + {% call accessLevel variable.readAccess %}var {% call mockedVariableName variable %}CallsCount = 0 + {% call accessLevel variable.readAccess %}var {% call mockedVariableName variable %}Called: Bool { + return {% call mockedVariableName variable %}CallsCount > 0 + } + {% call accessLevel variable.readAccess %}var {% call mockedVariableName variable %}: {{ variable.typeName }} { + get {% if variable.isAsync %}async {% endif %}{% if variable.throws %}throws {% endif %}{ + {% if variable.throws %} + {% call variableThrowableErrorUsage variable %} + {% endif %} + {% call mockedVariableName variable %}CallsCount += 1 + if let {% call variableClosureName variable %} = {% call variableClosureName variable %} { + return {{ 'try ' if variable.throws }}{{ 'await ' if variable.isAsync }}{% call variableClosureName variable %}() + } else { + return {% call underlyingMockedVariableName variable %} + } + } + } + {% call accessLevel variable.readAccess %}var {% call underlyingMockedVariableName variable %}: {{ variable.typeName }}{{ '!' if not variable.isOptional }} + {% if variable.throws %} + {% call variableThrowableErrorDeclaration variable %} + {% endif %} + {% call variableClosureDeclaration method %} +{% endmacro %} +{% macro underlyingMockedVariableName variable %}underlying{{ variable.name|upperFirstLetter }}{% endmacro %} +{% macro mockedVariableName variable %}{{ variable.name }}{% endmacro %} +{% for type in types.protocols where type.based.AutoMockable or type|annotated:"AutoMockable" %}{% if type.name != "AutoMockable" %} +{% call accessLevel type.accessLevel %}class {{ type.name | replace:"Protocol","" }}Mock: {{ type.name }} { + {% if type.accessLevel == "public" %}public init() {}{% endif %} +{% for variable in type.allVariables|!definedInExtension %} + {% if variable.isAsync or variable.throws %}{% call mockAsyncOrThrowingVariable variable %}{% elif variable.isOptional %}{% call mockOptionalVariable variable %}{% elif variable.isArray or variable.isDictionary %}{% call mockNonOptionalArrayOrDictionaryVariable variable %}{% else %}{% call mockNonOptionalVariable variable %}{% endif %} +{% endfor %} +{% if type.allMethods|static|count != 0 and type.allMethods|initializer|count != type.allMethods|static|count %} + static func reset() + { + {% for method in type.allMethods|static %} + {% call resetMethod method %} + {% endfor %} + } +{% endif %} +{% for method in type.allMethods|!definedInExtension %} + {% call mockMethod method %} +{% endfor %} +} +{% endif %}{% endfor %} + +// swiftlint:enable all diff --git a/Tools/Sourcery/sourcery_automockable_config.yml b/Tools/Sourcery/sourcery_automockable_config.yml new file mode 100644 index 000000000..5f4cb6f6e --- /dev/null +++ b/Tools/Sourcery/sourcery_automockable_config.yml @@ -0,0 +1,9 @@ +sources: + - ../../ElementX +templates: + - AutoMockable.stencil +output: + ../../ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +args: + automMockableTestableImports: [] + autoMockableImports: [Combine, MatrixRustSDK] \ No newline at end of file diff --git a/Tools/Sources/SetupProject.swift b/Tools/Sources/SetupProject.swift index 7fcdacc61..2c33b2e0b 100644 --- a/Tools/Sources/SetupProject.swift +++ b/Tools/Sources/SetupProject.swift @@ -15,7 +15,7 @@ struct SetupProject: ParsableCommand { } func brewBundleInstall() throws { - try Utilities.zsh("brew install xcodegen swiftgen swiftlint swiftformat git-lfs") + try Utilities.zsh("brew install xcodegen swiftgen swiftlint swiftformat git-lfs sourcery") } func xcodegen() throws { diff --git a/UnitTests/Sources/BugReportServiceTests.swift b/UnitTests/Sources/BugReportServiceTests.swift index 91afdae0e..9e8e50e35 100644 --- a/UnitTests/Sources/BugReportServiceTests.swift +++ b/UnitTests/Sources/BugReportServiceTests.swift @@ -19,7 +19,13 @@ import Foundation import XCTest class BugReportServiceTests: XCTestCase { - let bugReportService = MockBugReportService() + var bugReportService: BugReportServiceMock! + + override func setUpWithError() throws { + bugReportService = BugReportServiceMock() + bugReportService.underlyingCrashedLastRun = false + bugReportService.submitBugReportProgressListenerReturnValue = SubmitBugReportResponse(reportUrl: "https://www.example.com/123") + } func testInitialStateWithMockService() { XCTAssertFalse(bugReportService.crashedLastRun) diff --git a/UnitTests/Sources/BugReportViewModelTests.swift b/UnitTests/Sources/BugReportViewModelTests.swift index d5a0875cd..1b1d507b1 100644 --- a/UnitTests/Sources/BugReportViewModelTests.swift +++ b/UnitTests/Sources/BugReportViewModelTests.swift @@ -20,8 +20,12 @@ import XCTest @MainActor class BugReportViewModelTests: XCTestCase { + enum TestError: Error { + case testError + } + func testInitialState() { - let viewModel = BugReportViewModel(bugReportService: MockBugReportService(), + let viewModel = BugReportViewModel(bugReportService: BugReportServiceMock(), userID: "@mock.client.com", deviceID: nil, screenshot: nil, @@ -34,7 +38,7 @@ class BugReportViewModelTests: XCTestCase { } func testClearScreenshot() async throws { - let viewModel = BugReportViewModel(bugReportService: MockBugReportService(), + let viewModel = BugReportViewModel(bugReportService: BugReportServiceMock(), userID: "@mock.client.com", deviceID: nil, screenshot: UIImage.actions, @@ -47,7 +51,7 @@ class BugReportViewModelTests: XCTestCase { } func testAttachScreenshot() async throws { - let viewModel = BugReportViewModel(bugReportService: MockBugReportService(), + let viewModel = BugReportViewModel(bugReportService: BugReportServiceMock(), userID: "@mock.client.com", deviceID: nil, screenshot: nil, isModallyPresented: false) @@ -57,4 +61,54 @@ class BugReportViewModelTests: XCTestCase { try await Task.sleep(nanoseconds: 100_000_000) XCTAssert(context.viewState.screenshot == UIImage.actions) } + + func testSendReportWithSuccess() async throws { + let mockService = BugReportServiceMock() + mockService.submitBugReportProgressListenerReturnValue = SubmitBugReportResponse(reportUrl: "https://test.test") + let viewModel = BugReportViewModel(bugReportService: mockService, + userID: "@mock.client.com", + deviceID: nil, + screenshot: nil, isModallyPresented: false) + let context = viewModel.context + var isSuccess = false + viewModel.callback = { result in + switch result { + case .submitFinished: + isSuccess = true + default: break + } + } + context.send(viewAction: .submit) + try await Task.sleep(for: .milliseconds(100)) + XCTAssert(mockService.submitBugReportProgressListenerCallsCount == 1) + XCTAssert(mockService.submitBugReportProgressListenerReceivedArguments?.bugReport == BugReport(userID: "@mock.client.com", deviceID: nil, text: "", includeLogs: true, includeCrashLog: true, githubLabels: [], files: [])) + XCTAssertTrue(isSuccess) + } + + func testSendReportWithError() async throws { + let mockService = BugReportServiceMock() + mockService.submitBugReportProgressListenerClosure = { _, _ in + throw TestError.testError + } + let viewModel = BugReportViewModel(bugReportService: mockService, + userID: "@mock.client.com", + deviceID: nil, + screenshot: nil, isModallyPresented: false) + let context = viewModel.context + var isFailure = false + + viewModel.callback = { result in + switch result { + case .submitFailed: + isFailure = true + default: break + } + } + + context.send(viewAction: .submit) + try await Task.sleep(for: .milliseconds(100)) + XCTAssert(mockService.submitBugReportProgressListenerCallsCount == 1) + XCTAssert(mockService.submitBugReportProgressListenerReceivedArguments?.bugReport == BugReport(userID: "@mock.client.com", deviceID: nil, text: "", includeLogs: true, includeCrashLog: true, githubLabels: [], files: [])) + XCTAssertTrue(isFailure) + } } diff --git a/UnitTests/Sources/SessionVerificationStateMachineTests.swift b/UnitTests/Sources/SessionVerificationStateMachineTests.swift index f62eee464..99e6a4556 100644 --- a/UnitTests/Sources/SessionVerificationStateMachineTests.swift +++ b/UnitTests/Sources/SessionVerificationStateMachineTests.swift @@ -39,11 +39,11 @@ class SessionVerificationStateMachineTests: XCTestCase { stateMachine.processEvent(.didStartSasVerification) XCTAssertEqual(stateMachine.state, .sasVerificationStarted) - stateMachine.processEvent(.didReceiveChallenge(emojis: MockSessionVerificationControllerProxy.emojis)) - XCTAssertEqual(stateMachine.state, .showingChallenge(emojis: MockSessionVerificationControllerProxy.emojis)) + stateMachine.processEvent(.didReceiveChallenge(emojis: SessionVerificationControllerProxyMock.emojis)) + XCTAssertEqual(stateMachine.state, .showingChallenge(emojis: SessionVerificationControllerProxyMock.emojis)) stateMachine.processEvent(.acceptChallenge) - XCTAssertEqual(stateMachine.state, .acceptingChallenge(emojis: MockSessionVerificationControllerProxy.emojis)) + XCTAssertEqual(stateMachine.state, .acceptingChallenge(emojis: SessionVerificationControllerProxyMock.emojis)) stateMachine.processEvent(.didAcceptChallenge) XCTAssertEqual(stateMachine.state, .verified) @@ -61,11 +61,11 @@ class SessionVerificationStateMachineTests: XCTestCase { stateMachine.processEvent(.didStartSasVerification) XCTAssertEqual(stateMachine.state, .sasVerificationStarted) - stateMachine.processEvent(.didReceiveChallenge(emojis: MockSessionVerificationControllerProxy.emojis)) - XCTAssertEqual(stateMachine.state, .showingChallenge(emojis: MockSessionVerificationControllerProxy.emojis)) + stateMachine.processEvent(.didReceiveChallenge(emojis: SessionVerificationControllerProxyMock.emojis)) + XCTAssertEqual(stateMachine.state, .showingChallenge(emojis: SessionVerificationControllerProxyMock.emojis)) stateMachine.processEvent(.declineChallenge) - XCTAssertEqual(stateMachine.state, .decliningChallenge(emojis: MockSessionVerificationControllerProxy.emojis)) + XCTAssertEqual(stateMachine.state, .decliningChallenge(emojis: SessionVerificationControllerProxyMock.emojis)) stateMachine.processEvent(.didCancel) XCTAssertEqual(stateMachine.state, .cancelled) @@ -102,8 +102,8 @@ class SessionVerificationStateMachineTests: XCTestCase { stateMachine.processEvent(.didStartSasVerification) XCTAssertEqual(stateMachine.state, .sasVerificationStarted) - stateMachine.processEvent(.didReceiveChallenge(emojis: MockSessionVerificationControllerProxy.emojis)) - XCTAssertEqual(stateMachine.state, .showingChallenge(emojis: MockSessionVerificationControllerProxy.emojis)) + stateMachine.processEvent(.didReceiveChallenge(emojis: SessionVerificationControllerProxyMock.emojis)) + XCTAssertEqual(stateMachine.state, .showingChallenge(emojis: SessionVerificationControllerProxyMock.emojis)) stateMachine.processEvent(.cancel) XCTAssertEqual(stateMachine.state, .cancelling) diff --git a/UnitTests/Sources/SessionVerificationViewModelTests.swift b/UnitTests/Sources/SessionVerificationViewModelTests.swift index bf3e5913d..4dabc43e2 100644 --- a/UnitTests/Sources/SessionVerificationViewModelTests.swift +++ b/UnitTests/Sources/SessionVerificationViewModelTests.swift @@ -23,22 +23,22 @@ import XCTest class SessionVerificationViewModelTests: XCTestCase { var viewModel: SessionVerificationViewModelProtocol! var context: SessionVerificationViewModelType.Context! - var sessionVerificationController: SessionVerificationControllerProxyProtocol! + var sessionVerificationController: SessionVerificationControllerProxyMock! @MainActor override func setUpWithError() throws { - sessionVerificationController = MockSessionVerificationControllerProxy() + sessionVerificationController = SessionVerificationControllerProxyMock.configureMock() viewModel = SessionVerificationViewModel(sessionVerificationControllerProxy: sessionVerificationController) context = viewModel.context } - func testRequestVerification() async { + func testRequestVerification() async throws { XCTAssertEqual(context.viewState.verificationState, .initial) context.send(viewAction: .requestVerification) - await Task.yield() - + try await Task.sleep(for: .milliseconds(100)) + XCTAssert(sessionVerificationController.requestVerificationCallsCount == 1) XCTAssertEqual(context.viewState.verificationState, .requestingVerification) } @@ -53,7 +53,7 @@ class SessionVerificationViewModelTests: XCTestCase { XCTAssertEqual(context.viewState.verificationState, .cancelling) - try await Task.sleep(nanoseconds: 100_000_000) + try await Task.sleep(for: .milliseconds(100)) XCTAssertEqual(context.viewState.verificationState, .cancelled) @@ -62,6 +62,9 @@ class SessionVerificationViewModelTests: XCTestCase { await Task.yield() XCTAssertEqual(context.viewState.verificationState, .initial) + + XCTAssert(sessionVerificationController.requestVerificationCallsCount == 1) + XCTAssert(sessionVerificationController.cancelVerificationCallsCount == 1) } func testReceiveChallenge() { @@ -93,6 +96,7 @@ class SessionVerificationViewModelTests: XCTestCase { wait(for: [waitForAcceptance], timeout: 10.0) XCTAssertEqual(context.viewState.verificationState, .verified) + XCTAssert(sessionVerificationController.approveVerificationCallsCount == 1) } func testDeclineChallenge() { @@ -120,6 +124,7 @@ class SessionVerificationViewModelTests: XCTestCase { wait(for: [expectation], timeout: 10.0) XCTAssertEqual(context.viewState.verificationState, .cancelled) + XCTAssert(sessionVerificationController.declineVerificationCallsCount == 1) } // MARK: - Private @@ -157,6 +162,9 @@ class SessionVerificationViewModelTests: XCTestCase { XCTAssertEqual(context.viewState.verificationState, .sasVerificationStarted) wait(for: [verificationDataReceivalExpectation], timeout: 10.0) - XCTAssertEqual(context.viewState.verificationState, .showingChallenge(emojis: MockSessionVerificationControllerProxy.emojis)) + XCTAssertEqual(context.viewState.verificationState, .showingChallenge(emojis: SessionVerificationControllerProxyMock.emojis)) + + XCTAssert(sessionVerificationController.requestVerificationCallsCount == 1) + XCTAssert(sessionVerificationController.startSasVerificationCallsCount == 1) } } diff --git a/UnitTests/Sources/UserSession/UserSessionTests.swift b/UnitTests/Sources/UserSession/UserSessionTests.swift index a1da5e368..f41e4152b 100644 --- a/UnitTests/Sources/UserSession/UserSessionTests.swift +++ b/UnitTests/Sources/UserSession/UserSessionTests.swift @@ -40,9 +40,9 @@ final class UserSessionTests: XCTestCase { } .store(in: &cancellables) - let controller = MockSessionVerificationControllerProxy(callbacks: PassthroughSubject(), - isVerified: false, - requestDelay: .zero) + let controller = SessionVerificationControllerProxyMock.configureMock(callbacks: PassthroughSubject(), + isVerified: false, + requestDelay: .zero) clientProxy.sessionVerificationControllerProxyResult = .success(controller) clientProxy.callbacks.send(.receivedSyncUpdate) waitForExpectations(timeout: 1.0) @@ -50,9 +50,9 @@ final class UserSessionTests: XCTestCase { func test_whenUserSessionReceivesSyncUpdateAndSessionIsVerified_didVerifySessionEventReceived() throws { let expectation = expectation(description: "DidVerifySessionEvent expectation") - let controller = MockSessionVerificationControllerProxy(callbacks: PassthroughSubject(), - isVerified: false, - requestDelay: .zero) + let controller = SessionVerificationControllerProxyMock.configureMock(callbacks: PassthroughSubject(), + isVerified: false, + requestDelay: .zero) clientProxy.sessionVerificationControllerProxyResult = .success(controller) controller.callbacks.sink { value in diff --git a/changelog.d/600.feature b/changelog.d/600.feature new file mode 100644 index 000000000..78996ba95 --- /dev/null +++ b/changelog.d/600.feature @@ -0,0 +1 @@ +Auto Mocks generator added to the project. \ No newline at end of file