diff --git a/iOS/Layover/Layover.xcodeproj/project.pbxproj b/iOS/Layover/Layover.xcodeproj/project.pbxproj index 1903fab..b90f863 100644 --- a/iOS/Layover/Layover.xcodeproj/project.pbxproj +++ b/iOS/Layover/Layover.xcodeproj/project.pbxproj @@ -113,6 +113,10 @@ 19AE482A2B2A127E00DD4612 /* HLSAssetResourceLoaderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19AE48292B2A127E00DD4612 /* HLSAssetResourceLoaderDelegate.swift */; }; 19AE482C2B2A1A8B00DD4612 /* HLSSliceResourceLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19AE482B2B2A1A8B00DD4612 /* HLSSliceResourceLoader.swift */; }; 19AE482E2B2A24C700DD4612 /* URL+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19AE482D2B2A24C700DD4612 /* URL+.swift */; }; + 19B665D82B4EEDDD0083E63C /* SignUpViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19B665D42B4EEDDD0083E63C /* SignUpViewControllerTests.swift */; }; + 19B665D92B4EEDDD0083E63C /* SignUpWorkerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19B665D52B4EEDDD0083E63C /* SignUpWorkerTests.swift */; }; + 19B665DA2B4EEDDD0083E63C /* SignUpInteractorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19B665D62B4EEDDD0083E63C /* SignUpInteractorTests.swift */; }; + 19B665DB2B4EEDDD0083E63C /* SignUpPresenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19B665D72B4EEDDD0083E63C /* SignUpPresenterTests.swift */; }; 19C7AFCE2B02410F003B35F2 /* AuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19C7AFCD2B02410F003B35F2 /* AuthManager.swift */; }; 19C7AFD62B02584D003B35F2 /* KeychainStored.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19C7AFD52B02584D003B35F2 /* KeychainStored.swift */; }; 19E79AC02B0A85D0009EA9ED /* LoopingPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19E79ABF2B0A85D0009EA9ED /* LoopingPlayerView.swift */; }; @@ -368,6 +372,10 @@ 19AE48292B2A127E00DD4612 /* HLSAssetResourceLoaderDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HLSAssetResourceLoaderDelegate.swift; sourceTree = ""; }; 19AE482B2B2A1A8B00DD4612 /* HLSSliceResourceLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HLSSliceResourceLoader.swift; sourceTree = ""; }; 19AE482D2B2A24C700DD4612 /* URL+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+.swift"; sourceTree = ""; }; + 19B665D42B4EEDDD0083E63C /* SignUpViewControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignUpViewControllerTests.swift; sourceTree = ""; }; + 19B665D52B4EEDDD0083E63C /* SignUpWorkerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignUpWorkerTests.swift; sourceTree = ""; }; + 19B665D62B4EEDDD0083E63C /* SignUpInteractorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignUpInteractorTests.swift; sourceTree = ""; }; + 19B665D72B4EEDDD0083E63C /* SignUpPresenterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignUpPresenterTests.swift; sourceTree = ""; }; 19C7AFCD2B02410F003B35F2 /* AuthManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthManager.swift; sourceTree = ""; }; 19C7AFD52B02584D003B35F2 /* KeychainStored.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStored.swift; sourceTree = ""; }; 19E79ABF2B0A85D0009EA9ED /* LoopingPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopingPlayerView.swift; sourceTree = ""; }; @@ -619,6 +627,7 @@ 194C21C72B1DF09B00C62645 /* Scenes */ = { isa = PBXGroup; children = ( + 19B665D32B4EEDDD0083E63C /* SignUp */, 19AE481D2B29D02700DD4612 /* EditProfile */, 19AE48122B28C2A800DD4612 /* Setting */, 192513632B26F7BB001533FA /* TagPlayList */, @@ -793,6 +802,17 @@ path = HLSResourceLoader; sourceTree = ""; }; + 19B665D32B4EEDDD0083E63C /* SignUp */ = { + isa = PBXGroup; + children = ( + 19B665D42B4EEDDD0083E63C /* SignUpViewControllerTests.swift */, + 19B665D52B4EEDDD0083E63C /* SignUpWorkerTests.swift */, + 19B665D62B4EEDDD0083E63C /* SignUpInteractorTests.swift */, + 19B665D72B4EEDDD0083E63C /* SignUpPresenterTests.swift */, + ); + path = SignUp; + sourceTree = ""; + }; 19BB8A572B07BEE30070B922 /* UIComponents */ = { isa = PBXGroup; children = ( @@ -1436,7 +1456,6 @@ FC2511AB2B04EA6B004717BC /* MapConfigurator.swift in Sources */, 1945523B2B05258200299768 /* HomeConfigurator.swift in Sources */, FCF19BE22B2A4088003002E0 /* AVFileType+.swift in Sources */, - FC5BE11D2B148D160036366D /* EditProfileWorker.swift in Sources */, 19A1693A2B17BCC400DB34C0 /* MemberDTO.swift in Sources */, 194551F62B037F2D00299768 /* LoginViewController.swift in Sources */, FC767FA52B125F430088CF9B /* UIViewController+.swift in Sources */, @@ -1600,11 +1619,15 @@ 1925136D2B26F84E001533FA /* MockTagPlayListWorker.swift in Sources */, 19AE481A2B28C2B700DD4612 /* SettingPresenterTests.swift in Sources */, 19AE48172B28C2B700DD4612 /* SettingViewControllerTests.swift in Sources */, + 19B665DA2B4EEDDD0083E63C /* SignUpInteractorTests.swift in Sources */, + 19B665DB2B4EEDDD0083E63C /* SignUpPresenterTests.swift in Sources */, + 19B665D82B4EEDDD0083E63C /* SignUpViewControllerTests.swift in Sources */, 194C21C32B1DEE6B00C62645 /* HomeViewControllerTests.swift in Sources */, 192513692B26F7CE001533FA /* TagPlayListInteractorTests.swift in Sources */, 19AE48232B29D03D00DD4612 /* EditProfileInteractorTests.swift in Sources */, 194C21CC2B1DF39200C62645 /* MockHomeWorker.swift in Sources */, FC4E0C0E2B282AE500152596 /* UploadPostWorkerTests.swift in Sources */, + 19B665D92B4EEDDD0083E63C /* SignUpWorkerTests.swift in Sources */, FC4E0C112B28595200152596 /* MockUploadPostWorker.swift in Sources */, FC4E0C0D2B282AE500152596 /* UploadPostInteractorTests.swift in Sources */, ); diff --git a/iOS/Layover/Layover/Scenes/SignUpScene/SignUpInteractor.swift b/iOS/Layover/Layover/Scenes/SignUpScene/SignUpInteractor.swift index dd98c7d..3177943 100644 --- a/iOS/Layover/Layover/Scenes/SignUpScene/SignUpInteractor.swift +++ b/iOS/Layover/Layover/Scenes/SignUpScene/SignUpInteractor.swift @@ -10,8 +10,8 @@ import UIKit protocol SignUpBusinessLogic { func validateNickname(with request: SignUpModels.ValidateNickname.Request) - func checkDuplication(with request: SignUpModels.CheckDuplication.Request) - func signUp(with request: SignUpModels.SignUp.Request) + func checkDuplication(with request: SignUpModels.CheckDuplication.Request) async + func signUp(with request: SignUpModels.SignUp.Request) async } protocol SignUpDataStore: AnyObject { @@ -40,33 +40,29 @@ final class SignUpInteractor: SignUpBusinessLogic, SignUpDataStore { presenter?.presentNicknameValidation(with: SignUpModels.ValidateNickname.Response(nicknameState: response)) } - func checkDuplication(with request: SignUpModels.CheckDuplication.Request) { - Task { - let response = await userWorker?.checkNotDuplication(for: request.nickname) - await MainActor.run { - presenter?.presentNicknameDuplication(with: SignUpModels.CheckDuplication.Response(isValid: response ?? false)) - } + func checkDuplication(with request: SignUpModels.CheckDuplication.Request) async { + let response = await userWorker?.checkNotDuplication(for: request.nickname) + await MainActor.run { + presenter?.presentNicknameDuplication(with: SignUpModels.CheckDuplication.Response(isValid: response ?? false)) } } // MARK: - UseCase: SignUp - func signUp(with request: SignUpModels.SignUp.Request) { + func signUp(with request: SignUpModels.SignUp.Request) async { guard let signUpType, let socialToken else { return } - Task { - switch signUpType { - case .kakao: - if await signUpWorker?.signUp(withKakao: socialToken, username: request.nickname) == true { - await MainActor.run { - presenter?.presentSignUpSuccess() - } + switch signUpType { + case .kakao: + if await signUpWorker?.signUp(withKakao: socialToken, username: request.nickname) == true { + await MainActor.run { + presenter?.presentSignUpSuccess() } - case .apple: - if await signUpWorker?.signUp(withApple: socialToken, username: request.nickname) == true { - await MainActor.run { - presenter?.presentSignUpSuccess() - } + } + case .apple: + if await signUpWorker?.signUp(withApple: socialToken, username: request.nickname) == true { + await MainActor.run { + presenter?.presentSignUpSuccess() } } } diff --git a/iOS/Layover/Layover/Scenes/SignUpViewController.swift b/iOS/Layover/Layover/Scenes/SignUpViewController.swift index fe01214..dab52af 100644 --- a/iOS/Layover/Layover/Scenes/SignUpViewController.swift +++ b/iOS/Layover/Layover/Scenes/SignUpViewController.swift @@ -137,7 +137,9 @@ final class SignUpViewController: BaseViewController { @objc private func checkDuplicateNicknameButtonDidTap(_ sender: UIButton) { guard let nickname = nicknameTextfield.text else { return } checkDuplicateNicknameButton.isEnabled = false - interactor?.checkDuplication(with: SignUpModels.CheckDuplication.Request(nickname: nickname)) + Task { + await interactor?.checkDuplication(with: SignUpModels.CheckDuplication.Request(nickname: nickname)) + } } @objc private func popViewController() { @@ -146,7 +148,9 @@ final class SignUpViewController: BaseViewController { @objc private func signUpButtonDidTap(_ sender: UIButton) { guard let nickname = nicknameTextfield.text else { return } - interactor?.signUp(with: SignUpModels.SignUp.Request(nickname: nickname)) + Task { + await interactor?.signUp(with: SignUpModels.SignUp.Request(nickname: nickname)) + } } } diff --git a/iOS/Layover/Layover/Workers/Mocks/MockSignUpWorker.swift b/iOS/Layover/Layover/Workers/Mocks/MockSignUpWorker.swift index 2bfc300..6198cf3 100644 --- a/iOS/Layover/Layover/Workers/Mocks/MockSignUpWorker.swift +++ b/iOS/Layover/Layover/Workers/Mocks/MockSignUpWorker.swift @@ -9,7 +9,7 @@ import Foundation import OSLog -final class MockSignUpWorker { +class MockSignUpWorker: SignUpWorkerProtocol { // MARK: - Properties @@ -21,11 +21,6 @@ final class MockSignUpWorker { authManager: StubAuthManager())) { self.provider = provider } -} - -// MARK: - SignUpWorkerProtocol - -extension MockSignUpWorker: SignUpWorkerProtocol { func signUp(withKakao socialToken: String, username: String) async -> Bool { guard let mockFileLocation = Bundle.main.url(forResource: "LoginData", withExtension: "json"), @@ -86,5 +81,4 @@ extension MockSignUpWorker: SignUpWorkerProtocol { return false } } - } diff --git a/iOS/Layover/LayoverTests/Mocks/Workers/MockUserWorker.swift b/iOS/Layover/LayoverTests/Mocks/Workers/MockUserWorker.swift index 117dfb1..5faa754 100644 --- a/iOS/Layover/LayoverTests/Mocks/Workers/MockUserWorker.swift +++ b/iOS/Layover/LayoverTests/Mocks/Workers/MockUserWorker.swift @@ -9,7 +9,7 @@ import Foundation import OSLog -final class MockUserWorker: UserWorkerProtocol { +class MockUserWorker: UserWorkerProtocol { // MARK: - Properties diff --git a/iOS/Layover/LayoverTests/Scenes/SignUp/SignUpInteractorTests.swift b/iOS/Layover/LayoverTests/Scenes/SignUp/SignUpInteractorTests.swift new file mode 100644 index 0000000..7d83d66 --- /dev/null +++ b/iOS/Layover/LayoverTests/Scenes/SignUp/SignUpInteractorTests.swift @@ -0,0 +1,159 @@ +// +// SignUpInteractorTests.swift +// Layover +// +// Created by 김인환 on 1/6/24. +// Copyright (c) 2024 CodeBomber. All rights reserved. +// +// This file was generated by the Clean Swift Xcode Templates so +// you can apply clean architecture to your iOS and Mac projects, +// see http://clean-swift.com +// + +@testable import Layover +import XCTest + +final class SignUpInteractorTests: XCTestCase { + // MARK: Subject under test + + var sut: SignUpInteractor! + + typealias Models = SignUpModels + + // MARK: - Test lifecycle + + override func setUp() { + super.setUp() + setupSignUpInteractor() + } + + // MARK: - Test setup + + func setupSignUpInteractor() { + sut = SignUpInteractor() + } + + // MARK: - Test doubles + + final class SignUpPresentationLogicSpy: SignUpPresentationLogic { + var presentSignUpSuccessDidCalled = false + var presentNicknameValidationDidCalled = false + var presentNicknameValidationResponse: Models.ValidateNickname.Response! + var presentNicknameDuplicationDidCalled = false + var presentNicknameDupllicationResponse: Models.CheckDuplication.Response! + + func presentSignUpSuccess() { + presentSignUpSuccessDidCalled = true + } + + func presentNicknameValidation(with response: Models.ValidateNickname.Response) { + presentNicknameValidationDidCalled = true + presentNicknameValidationResponse = response + } + + func presentNicknameDuplication(with response: Models.CheckDuplication.Response) { + presentNicknameDuplicationDidCalled = true + presentNicknameDupllicationResponse = response + } + } + + final class UserWorkerSpy: MockUserWorker { + var validateNicknameCalled = false + var checkNotDuplicationCalled = false + + override func validateNickname(_ nickname: String) -> NicknameState { + validateNicknameCalled = true + return super.validateNickname(nickname) + } + + override func checkNotDuplication(for userName: String) async -> Bool? { + checkNotDuplicationCalled = true + return await super.checkNotDuplication(for: userName) + } + } + + final class SignUpWorkerSpy: MockSignUpWorker { + var signUpWithKakaoCalled = false + var signUpWithAppleCalled = false + + override func signUp(withKakao token: String, username: String) async -> Bool { + signUpWithKakaoCalled = true + return await super.signUp(withKakao: token, username: username) + } + + override func signUp(withApple identityToken: String, username: String) async -> Bool { + signUpWithAppleCalled = true + return await super.signUp(withApple: identityToken, username: username) + } + } + + // MARK: - Tests + + func test_유효한_닉네임값으로_validateNickname을_호출하면_UserWorker를_통해_닉네임_유효성_검사를_요청하고_valid_결과를_presenter의_presentNicknameValidation를_호출한다() { + // arrange + let presenterSpy = SignUpPresentationLogicSpy() + sut.presenter = presenterSpy + let userWorkerSpy = UserWorkerSpy() + sut.userWorker = userWorkerSpy + + // act + sut.validateNickname(with: Models.ValidateNickname.Request(nickname: "안유진")) + + // assert + XCTAssertTrue(userWorkerSpy.validateNicknameCalled) + XCTAssertTrue(presenterSpy.presentNicknameValidationDidCalled) + XCTAssertNotNil(presenterSpy.presentNicknameValidationResponse.nicknameState) + } + + func test_checkDuplication을_호출하면_UserWorker를_통해_닉네임_중복_검사를_요청하고_결과를_presenter의_presentNicknameDuplication를_호출하여_전달한다() async { + // arrange + let presenterSpy = SignUpPresentationLogicSpy() + sut.presenter = presenterSpy + let userWorkerSpy = UserWorkerSpy() + sut.userWorker = userWorkerSpy + let signUpWorkerSpy = SignUpWorkerSpy() + sut.signUpWorker = signUpWorkerSpy + + // act + await sut.checkDuplication(with: Models.CheckDuplication.Request(nickname: "안유진")) + + // assert + XCTAssertTrue(userWorkerSpy.checkNotDuplicationCalled) + XCTAssertTrue(presenterSpy.presentNicknameDuplicationDidCalled) + XCTAssertNotNil(presenterSpy.presentNicknameDupllicationResponse.isValid) + } + + func test_카카오_signUp을_호출하면_SignUpWorker를_통해_회원가입을_요청하고_presenter의_presentSignUpSuccess를_호출한다() async { + // arrange + let presenterSpy = SignUpPresentationLogicSpy() + sut.presenter = presenterSpy + let signUpWorkerSpy = SignUpWorkerSpy() + signUpWorkerSpy.signUpWithKakaoCalled = true + sut.signUpWorker = signUpWorkerSpy + sut.signUpType = .kakao + sut.socialToken = "1234" + + // act + await sut.signUp(with: Models.SignUp.Request(nickname: "안유진")) + + // assert + XCTAssertTrue(presenterSpy.presentSignUpSuccessDidCalled) + } + + func test_애플_signUp을_호출하면_SignUpWorker를_통해_회원가입을_요청하고_presenter의_presentSignUpSuccess를_호출한다() async { + // arrange + let presenterSpy = SignUpPresentationLogicSpy() + sut.presenter = presenterSpy + let signUpWorkerSpy = SignUpWorkerSpy() + signUpWorkerSpy.signUpWithAppleCalled = true + sut.signUpWorker = signUpWorkerSpy + sut.signUpType = .apple + sut.socialToken = "1234" + + // act + await sut.signUp(with: Models.SignUp.Request(nickname: "안유진")) + + // assert + XCTAssertTrue(presenterSpy.presentSignUpSuccessDidCalled) + } +} diff --git a/iOS/Layover/LayoverTests/Scenes/SignUp/SignUpPresenterTests.swift b/iOS/Layover/LayoverTests/Scenes/SignUp/SignUpPresenterTests.swift new file mode 100644 index 0000000..6f6e876 --- /dev/null +++ b/iOS/Layover/LayoverTests/Scenes/SignUp/SignUpPresenterTests.swift @@ -0,0 +1,62 @@ +//// +//// SignUpPresenterTests.swift +//// Layover +//// +//// Created by 김인환 on 1/6/24. +//// Copyright (c) 2024 CodeBomber. All rights reserved. +//// +//// This file was generated by the Clean Swift Xcode Templates so +//// you can apply clean architecture to your iOS and Mac projects, +//// see http://clean-swift.com +//// +// +//@testable import Layover +//import XCTest +// +//class SignUpPresenterTests: XCTestCase { +// // MARK: Subject under test +// +// var sut: SignUpPresenter! +// +// // MARK: - Test lifecycle +// +// override func setUp() { +// super.setUp() +// setupSignUpPresenter() +// } +// +// override func tearDown() { +// super.tearDown() +// } +// +// // MARK: - Test setup +// +// func setupSignUpPresenter() { +// sut = SignUpPresenter() +// } +// +// // MARK: - Test doubles +// +// class SignUpDisplayLogicSpy: SignUpDisplayLogic { +// var displaySomethingCalled = false +// +// func displaySomething(viewModel: SignUp.Something.ViewModel) { +// displaySomethingCalled = true +// } +// } +// +// // MARK: - Tests +// +// func testPresentSomething() { +// // Given +// let spy = SignUpDisplayLogicSpy() +// sut.viewController = spy +// let response = SignUp.Something.Response() +// +// // When +// sut.presentSomething(response: response) +// +// // Then +// XCTAssertTrue(spy.displaySomethingCalled, "presentSomething(response:) should ask the view controller to display the result") +// } +//} diff --git a/iOS/Layover/LayoverTests/Scenes/SignUp/SignUpViewControllerTests.swift b/iOS/Layover/LayoverTests/Scenes/SignUp/SignUpViewControllerTests.swift new file mode 100644 index 0000000..85c1685 --- /dev/null +++ b/iOS/Layover/LayoverTests/Scenes/SignUp/SignUpViewControllerTests.swift @@ -0,0 +1,84 @@ +//// +//// SignUpViewControllerTests.swift +//// Layover +//// +//// Created by 김인환 on 1/6/24. +//// Copyright (c) 2024 CodeBomber. All rights reserved. +//// +//// This file was generated by the Clean Swift Xcode Templates so +//// you can apply clean architecture to your iOS and Mac projects, +//// see http://clean-swift.com +//// +// +//@testable import Layover +//import XCTest +// +//class SignUpViewControllerTests: XCTestCase { +// // MARK: Subject under test +// +// var sut: SignUpViewController! +// var window: UIWindow! +// +// // MARK: - Test lifecycle +// +// override func setUp() { +// super.setUp() +// window = UIWindow() +// setupSignUpViewController() +// } +// +// override func tearDown() { +// window = nil +// super.tearDown() +// } +// +// // MARK: - Test setup +// +// func setupSignUpViewController() { +// let bundle = Bundle.main +// let storyboard = UIStoryboard(name: "Main", bundle: bundle) +// sut = storyboard.instantiateViewController(withIdentifier: "SignUpViewController") as! SignUpViewController +// } +// +// func loadView() { +// window.addSubview(sut.view) +// RunLoop.current.run(until: Date()) +// } +// +// // MARK: - Test doubles +// +// class SignUpBusinessLogicSpy: SignUpBusinessLogic { +// var doSomethingCalled = false +// +// func doSomething(request: SignUp.Something.Request) +// { +// doSomethingCalled = true +// } +// } +// +// // MARK: - Tests +// +// func testShouldDoSomethingWhenViewIsLoaded() { +// // Given +// let spy = SignUpBusinessLogicSpy() +// sut.interactor = spy +// +// // When +// loadView() +// +// // Then +// XCTAssertTrue(spy.doSomethingCalled, "viewDidLoad() should ask the interactor to do something") +// } +// +// func testDisplaySomething() { +// // Given +// let viewModel = SignUp.Something.ViewModel() +// +// // When +// loadView() +// sut.displaySomething(viewModel: viewModel) +// +// // Then +// //XCTAssertEqual(sut.nameTextField.text, "", "displaySomething(viewModel:) should update the name text field") +// } +//} diff --git a/iOS/Layover/LayoverTests/Scenes/SignUp/SignUpWorkerTests.swift b/iOS/Layover/LayoverTests/Scenes/SignUp/SignUpWorkerTests.swift new file mode 100644 index 0000000..9152372 --- /dev/null +++ b/iOS/Layover/LayoverTests/Scenes/SignUp/SignUpWorkerTests.swift @@ -0,0 +1,50 @@ +// +// SignUpWorkerTests.swift +// Layover +// +// Created by 김인환 on 1/6/24. +// Copyright (c) 2024 CodeBomber. All rights reserved. +// +// This file was generated by the Clean Swift Xcode Templates so +// you can apply clean architecture to your iOS and Mac projects, +// see http://clean-swift.com +// + +@testable import Layover +import XCTest + +class SignUpWorkerTests: XCTestCase +{ + // MARK: Subject under test + + var sut: SignUpWorker! + + // MARK: - Test lifecycle + + override func setUp() { + super.setUp() + setupSignUpWorker() + } + + override func tearDown() { + super.tearDown() + } + + // MARK: - Test setup + + func setupSignUpWorker() { + sut = SignUpWorker() + } + + // MARK: - Test doubles + + // MARK: - Tests + + func testSomething() { + // Given + + // When + + // Then + } +}