From 55c07d75897facf56505f0e82f7e44e35f76e88e Mon Sep 17 00:00:00 2001 From: kong <1018dbrud@gmail.com> Date: Tue, 12 Dec 2023 17:59:40 +0900 Subject: [PATCH 1/8] =?UTF-8?q?=E2=9C=85=20UploadPost=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=ED=8C=8C=EC=9D=BC=20=EC=83=9D=EC=84=B1=20=EB=B0=8F?= =?UTF-8?q?=20UploadPostPresenterTests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Layover/Layover.xcodeproj/project.pbxproj | 24 +++ .../UploadPostInteractorTests.swift | 139 ++++++++++++ .../UploadPost/UploadPostPresenterTests.swift | 131 +++++++++++ .../UploadPostViewControllerTests.swift | 203 ++++++++++++++++++ .../UploadPost/UploadPostWorkerTests.swift | 75 +++++++ 5 files changed, 572 insertions(+) create mode 100644 iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostInteractorTests.swift create mode 100644 iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostPresenterTests.swift create mode 100644 iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostViewControllerTests.swift create mode 100644 iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostWorkerTests.swift diff --git a/iOS/Layover/Layover.xcodeproj/project.pbxproj b/iOS/Layover/Layover.xcodeproj/project.pbxproj index 8c2b3a0..7e253d4 100644 --- a/iOS/Layover/Layover.xcodeproj/project.pbxproj +++ b/iOS/Layover/Layover.xcodeproj/project.pbxproj @@ -161,6 +161,10 @@ FC4975932B03432800D8627F /* Pretendard-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = FC49758A2B03432800D8627F /* Pretendard-Bold.ttf */; }; FC4975942B03432800D8627F /* Pretendard-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = FC49758B2B03432800D8627F /* Pretendard-Regular.ttf */; }; FC4975992B03439000D8627F /* UIFont+.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4975982B03439000D8627F /* UIFont+.swift */; }; + FC4E0C0C2B282AE500152596 /* UploadPostViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4E0C082B282AE500152596 /* UploadPostViewControllerTests.swift */; }; + FC4E0C0D2B282AE500152596 /* UploadPostInteractorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4E0C092B282AE500152596 /* UploadPostInteractorTests.swift */; }; + FC4E0C0E2B282AE500152596 /* UploadPostWorkerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4E0C0A2B282AE500152596 /* UploadPostWorkerTests.swift */; }; + FC4E0C0F2B282AE500152596 /* UploadPostPresenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4E0C0B2B282AE500152596 /* UploadPostPresenterTests.swift */; }; FC5BE11C2B148D160036366D /* EditProfilePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5BE1162B148D160036366D /* EditProfilePresenter.swift */; }; FC5BE11D2B148D160036366D /* EditProfileWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5BE1172B148D160036366D /* EditProfileWorker.swift */; }; FC5BE11E2B148D160036366D /* EditProfileRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5BE1182B148D160036366D /* EditProfileRouter.swift */; }; @@ -378,6 +382,10 @@ FC49758A2B03432800D8627F /* Pretendard-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-Bold.ttf"; sourceTree = ""; }; FC49758B2B03432800D8627F /* Pretendard-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-Regular.ttf"; sourceTree = ""; }; FC4975982B03439000D8627F /* UIFont+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+.swift"; sourceTree = ""; }; + FC4E0C082B282AE500152596 /* UploadPostViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadPostViewControllerTests.swift; sourceTree = ""; }; + FC4E0C092B282AE500152596 /* UploadPostInteractorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadPostInteractorTests.swift; sourceTree = ""; }; + FC4E0C0A2B282AE500152596 /* UploadPostWorkerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadPostWorkerTests.swift; sourceTree = ""; }; + FC4E0C0B2B282AE500152596 /* UploadPostPresenterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadPostPresenterTests.swift; sourceTree = ""; }; FC5BE1162B148D160036366D /* EditProfilePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfilePresenter.swift; sourceTree = ""; }; FC5BE1172B148D160036366D /* EditProfileWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfileWorker.swift; sourceTree = ""; }; FC5BE1182B148D160036366D /* EditProfileRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfileRouter.swift; sourceTree = ""; }; @@ -509,6 +517,7 @@ isa = PBXGroup; children = ( 194C21BE2B1DEE5500C62645 /* Home */, + FCF1A02D2B275A0400D961BE /* UploadPost */, ); path = Scenes; sourceTree = ""; @@ -1035,6 +1044,17 @@ path = SignUpScene; sourceTree = ""; }; + FCF1A02D2B275A0400D961BE /* UploadPost */ = { + isa = PBXGroup; + children = ( + FC4E0C082B282AE500152596 /* UploadPostViewControllerTests.swift */, + FC4E0C092B282AE500152596 /* UploadPostInteractorTests.swift */, + FC4E0C0A2B282AE500152596 /* UploadPostWorkerTests.swift */, + FC4E0C0B2B282AE500152596 /* UploadPostPresenterTests.swift */, + ); + path = UploadPost; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -1366,10 +1386,14 @@ buildActionMask = 2147483647; files = ( 194C21C42B1DEE6B00C62645 /* HomeInteractorTests.swift in Sources */, + FC4E0C0F2B282AE500152596 /* UploadPostPresenterTests.swift in Sources */, + FC4E0C0C2B282AE500152596 /* UploadPostViewControllerTests.swift in Sources */, 194C21C52B1DEE6B00C62645 /* HomeWorkerTests.swift in Sources */, 194C21C62B1DEE6B00C62645 /* HomePresenterTests.swift in Sources */, 194C21C32B1DEE6B00C62645 /* HomeViewControllerTests.swift in Sources */, 194C21CC2B1DF39200C62645 /* MockHomeWorker.swift in Sources */, + FC4E0C0E2B282AE500152596 /* UploadPostWorkerTests.swift in Sources */, + FC4E0C0D2B282AE500152596 /* UploadPostInteractorTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostInteractorTests.swift b/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostInteractorTests.swift new file mode 100644 index 0000000..ec40997 --- /dev/null +++ b/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostInteractorTests.swift @@ -0,0 +1,139 @@ +// +// UploadPostInteractorTests.swift +// LayoverTests +// +// Created by kong on 2023/12/12. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +@testable import Layover +import XCTest + +//class UploadPostInteractorTests: XCTestCase { +// +// // MARK: - Subject Under Test (SUT) +// +// typealias Models = UploadPostModels +// var sut: UploadPostInteractor! +// +// // MARK: - Test Lifecycle +// +// override func setUp() { +// super.setUp() +// setupUploadPostInteractor() +// } +// +// override func tearDown() { +// sut = nil +// super.tearDown() +// } +// +// // MARK: - Test Setup +// +// func setupUploadPostInteractor() { +// sut = UploadPostInteractor() +// } +// +// // MARK: - Test Doubles +// +// class UploadPostPresentationLogicSpy: UploadPostPresentationLogic { +// +// // MARK: Spied Methods +// +// var presentFetchFromLocalDataStoreCalled = false +// var fetchFromLocalDataStoreResponse: UploadPostModels.FetchFromLocalDataStore.Response! +// func presentFetchFromLocalDataStore(with response: UploadPostModels.FetchFromLocalDataStore.Response) { +// presentFetchFromLocalDataStoreCalled = true +// fetchFromLocalDataStoreResponse = response +// } +// +// var presentFetchFromRemoteDataStoreCalled = false +// var fetchFromRemoteDataStoreResponse: UploadPostModels.FetchFromRemoteDataStore.Response! +// func presentFetchFromRemoteDataStore(with response: UploadPostModels.FetchFromRemoteDataStore.Response) { +// presentFetchFromRemoteDataStoreCalled = true +// fetchFromRemoteDataStoreResponse = response +// } +// +// var presentTrackAnalyticsCalled = false +// var trackAnalyticsResponse: UploadPostModels.TrackAnalytics.Response! +// func presentTrackAnalytics(with response: UploadPostModels.TrackAnalytics.Response) { +// presentTrackAnalyticsCalled = true +// trackAnalyticsResponse = response +// } +// +// var presentPerformUploadPostCalled = false +// var performUploadPostResponse: UploadPostModels.PerformUploadPost.Response! +// func presentPerformUploadPost(with response: UploadPostModels.PerformUploadPost.Response) { +// presentPerformUploadPostCalled = true +// performUploadPostResponse = response +// } +// } +// +// class UploadPostWorkerSpy: UploadPostWorker { +// +// // MARK: Spied Methods +// +// var validateExampleVariableCalled = false +// override func validate(exampleVariable: String?) -> Models.UploadPostError? { +// validateExampleVariableCalled = true +// return super.validate(exampleVariable: exampleVariable) +// } +// } +// +// // MARK: - Tests +// +// func testFetchFromLocalDataStoreShouldAskPresenterToFormat() { +// // given +// let spy = UploadPostPresentationLogicSpy() +// sut.presenter = spy +// let request = Models.FetchFromLocalDataStore.Request() +// +// // when +// sut.fetchFromLocalDataStore(with: request) +// +// // then +// XCTAssertTrue(spy.presentFetchFromLocalDataStoreCalled, "fetchFromLocalDataStore(with:) should ask the presenter to format the result") +// } +// +// func testTrackAnalyticsShouldAskPresenterToFormat() { +// // given +// let spy = UploadPostPresentationLogicSpy() +// sut.presenter = spy +// let request = Models.TrackAnalytics.Request(event: .screenView) +// +// // when +// sut.trackAnalytics(with: request) +// +// // then +// XCTAssertTrue(spy.presentTrackAnalyticsCalled, "trackAnalytics(with:) should ask the presenter to format the result") +// } +// +// func testPerformUploadPostShouldValidateExampleVariable() { +// // given +// let spy = UploadPostWorkerSpy() +// sut.worker = spy +// let request = Models.PerformUploadPost.Request() +// +// // when +// sut.performUploadPost(with: request) +// +// // then +// XCTAssertTrue(spy.validateExampleVariableCalled, "performUploadPost(with:) should ask the worker to validate the example variable") +// } +// +// func testPerformUploadPostShouldAskPresenterToFormat() { +// // given +// let spy = UploadPostPresentationLogicSpy() +// sut.presenter = spy +// let request = Models.PerformUploadPost.Request() +// +// // when +// let expect = expectation(description: "Wait for performUploadPost(with:) to return") +// sut.performUploadPost(with: request) +// expect.fulfill() +// waitForExpectations(timeout: 1) +// +// // then +// XCTAssertTrue(spy.presentPerformUploadPostCalled, "performUploadPost(with:) should ask the presenter to format the result") +// } +//} diff --git a/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostPresenterTests.swift b/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostPresenterTests.swift new file mode 100644 index 0000000..bf34e1f --- /dev/null +++ b/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostPresenterTests.swift @@ -0,0 +1,131 @@ +// +// UploadPostPresenterTests.swift +// LayoverTests +// +// Created by kong on 2023/12/12. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +@testable import Layover +import XCTest + +class UploadPostPresenterTests: XCTestCase { + + // MARK: - Subject Under Test (SUT) + + typealias Models = UploadPostModels + var sut: UploadPostPresenter! + + // MARK: - Test Lifecycle + + override func setUp() { + super.setUp() + setupUploadPostPresenter() + } + + override func tearDown() { + sut = nil + super.tearDown() + } + + // MARK: - Test Setup + + func setupUploadPostPresenter() { + sut = UploadPostPresenter() + } + + // MARK: - Test Doubles + + class UploadPostDisplayLogicSpy: UploadPostDisplayLogic { + + // MARK: Spied Methods + + var displayTagsCalled = false + var displayTagsViewModel: UploadPostModels.FetchTags.ViewModel! + func displayTags(viewModel: Layover.UploadPostModels.FetchTags.ViewModel) { + displayTagsCalled = true + displayTagsViewModel = viewModel + + } + + var displayThumbnailCalled = false + var displayThumbnailViewModel: UploadPostModels.FetchThumbnail.ViewModel! + func displayThumbnail(viewModel: Layover.UploadPostModels.FetchThumbnail.ViewModel) { + displayThumbnailCalled = true + displayThumbnailViewModel = viewModel + } + + var displayCurrentAddressCalled = false + var displayCurrentAddressViewModel: UploadPostModels.FetchCurrentAddress.ViewModel! + func displayCurrentAddress(viewModel: Layover.UploadPostModels.FetchCurrentAddress.ViewModel) { + displayCurrentAddressCalled = true + displayCurrentAddressViewModel = viewModel + } + + var displayUploadButtonCalled = false + var displayUploadButtonViewModel: UploadPostModels.CanUploadPost.ViewModel! + func displayUploadButton(viewModel: Layover.UploadPostModels.CanUploadPost.ViewModel) { + displayUploadButtonCalled = true + displayUploadButtonViewModel = viewModel + } + + } + + // MARK: - Tests + + func test_presentTags를_실행하면_displayTags를_실행한다() { + // given + let spy = UploadPostDisplayLogicSpy() + sut.viewController = spy + let response = Models.FetchTags.Response(tags: []) + + // when + sut.presentTags(with: response) + + // then + XCTAssertTrue(spy.displayTagsCalled, "presentTags(with:) should ask the view controller to display the result") + } + + func test_presentThumbnail을_실행하면_displayThumbnail을_실행한다() { + // given + let spy = UploadPostDisplayLogicSpy() + sut.viewController = spy + let thumbnailImage = UIImage(resource: .content).cgImage! + let response = Models.FetchThumbnail.Response(thumbnailImage: thumbnailImage) + + // when + sut.presentThumbnailImage(with: response) + + // then + XCTAssertTrue(spy.displayThumbnailCalled, "presentThumbnailImage(with:) should ask the view controller to display the result") + } + + func test_presentCurrentAddress를_실행하면_displayCurrentAddress를_실행한다() { + // given + let spy = UploadPostDisplayLogicSpy() + sut.viewController = spy + let response = Models.FetchCurrentAddress.Response(administrativeArea: nil, + locality: nil, + subLocality: nil) + + // when + sut.presentCurrentAddress(with: response) + + // then + XCTAssertTrue(spy.displayCurrentAddressCalled, "presentCurrentAddress(with:) should ask the view controller to display the result") + } + + func test_presentUploadButton를_실행하면_displayUploadButton을_실행한다() { + // given + let spy = UploadPostDisplayLogicSpy() + sut.viewController = spy + let response = Models.CanUploadPost.Response(isEmpty: true) + + // when + sut.presentUploadButton(with: response) + + // then + XCTAssertTrue(spy.displayUploadButtonCalled, "presentUploadButton(with:) should ask the view controller to display the result") + } + +} diff --git a/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostViewControllerTests.swift b/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostViewControllerTests.swift new file mode 100644 index 0000000..190a498 --- /dev/null +++ b/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostViewControllerTests.swift @@ -0,0 +1,203 @@ +// +// UploadPostViewControllerTests.swift +// LayoverTests +// +// Created by kong on 2023/12/12. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +@testable import Layover +import XCTest + +//class UploadPostViewControllerTests: XCTestCase { +// +// // MARK: - Subject Under Test (SUT) +// +// typealias Models = UploadPostModels +// var sut: UploadPostViewController! +// var window: UIWindow! +// +// // MARK: - Test Lifecycle +// +// override func setUp() { +// super.setUp() +// window = UIWindow() +// setupUploadPostViewController() +// } +// +// override func tearDown() { +// window = nil +// sut = nil +// super.tearDown() +// } +// +// // MARK: - Test Setup +// +// func setupUploadPostViewController() { +// let bundle = Bundle.main +// let storyboard = UIStoryboard(name: "Main", bundle: bundle) +// sut = storyboard.instantiateViewController(withIdentifier: "UploadPostViewController") as? UploadPostViewController +// } +// +// func loadView() { +// window.addSubview(sut.view) +// RunLoop.current.run(until: Date()) +// } +// +// // MARK: - Test Doubles +// +// class UploadPostBusinessLogicSpy: UploadPostBusinessLogic { +// +// // MARK: Spied Methods +// +// var fetchFromLocalDataStoreCalled = false +// func fetchFromLocalDataStore(with request: UploadPostModels.FetchFromLocalDataStore.Request) { +// fetchFromLocalDataStoreCalled = true +// } +// +// var fetchFromRemoteDataStoreCalled = false +// func fetchFromRemoteDataStore(with request: UploadPostModels.FetchFromRemoteDataStore.Request) { +// fetchFromRemoteDataStoreCalled = true +// } +// +// var trackAnalyticsCalled = false +// func trackAnalytics(with request: UploadPostModels.TrackAnalytics.Request) { +// trackAnalyticsCalled = true +// } +// +// var performUploadPostCalled = false +// func performUploadPost(with request: UploadPostModels.PerformUploadPost.Request) { +// performUploadPostCalled = true +// } +// } +// +// class UploadPostRouterSpy: UploadPostRouter { +// +// // MARK: Spied Methods +// +// var routeToNextCalled = false +// override func routeToNext() { +// routeToNextCalled = true +// } +// } +// +// // MARK: - Tests +// +// func testShouldFetchFromLocalDataStoreWhenViewIsLoaded() { +// // given +// let spy = UploadPostBusinessLogicSpy() +// sut.interactor = spy +// +// // when +// loadView() +// +// // then +// XCTAssertTrue(spy.fetchFromLocalDataStoreCalled, "viewDidLoad() should ask the interactor to fetch from local DataStore") +// } +// +// func testShouldDisplayDataFetchedFromLocalDataStore() { +// // given +// loadView() +// let translation = "Example string." +// let viewModel = Models.FetchFromLocalDataStore.ViewModel(exampleTranslation: translation) +// +// // when +// sut.displayFetchFromLocalDataStore(with: viewModel) +// +// // then +// XCTAssertEqual(sut.exampleLocalLabel.text, translation, "displayFetchFromLocalDataStore(with:) should display the correct example label text") +// } +// +// func testShouldFetchFromRemoteDataStoreWhenViewWillAppear() { +// // given +// let spy = UploadPostBusinessLogicSpy() +// sut.interactor = spy +// +// // when +// loadView() +// +// // then +// XCTAssertTrue(spy.fetchFromRemoteDataStoreCalled, "viewWillAppear(_:) should ask the interactor to fetch from remote DataStore") +// } +// +// func testShouldDisplayDataFetchedFromRemoteDataStore() { +// // given +// loadView() +// let exampleVariable = "Example string." +// let viewModel = Models.FetchFromRemoteDataStore.ViewModel(exampleVariable: exampleVariable) +// +// // when +// sut.displayFetchFromRemoteDataStore(with: viewModel) +// +// // then +// XCTAssertEqual(sut.exampleRemoteLabel.text, exampleVariable, "displayFetchFromRemoteDataStore(with:) should display the correct example label text") +// } +// +// func testShouldTrackAnalyticsWhenViewDidAppear() { +// // given +// let spy = UploadPostBusinessLogicSpy() +// sut.interactor = spy +// loadView() +// +// // when +// sut.viewDidAppear(true) +// +// // then +// XCTAssertTrue(spy.trackAnalyticsCalled, "When needed, view controller should ask the interactor to track analytics") +// } +// +// func testShouldDisplayTrackAnalyticsWhenDisplayTrackAnalytics() { +// // given +// loadView() +// let viewModel = Models.TrackAnalytics.ViewModel() +// +// // when +// sut.displayTrackAnalytics(with: viewModel) +// +// // then +// // assert something here based on use case +// } +// +// func testUnsuccessfulUploadPostShouldShowErrorAsLabel() { +// // given +// loadView() +// var error = Models.UploadPostError(type: .emptyExampleVariable) +// error.message = "Example error" +// let viewModel = Models.PerformUploadPost.ViewModel(error: error) +// +// // when +// sut.displayPerformUploadPost(with: viewModel) +// +// // then +// XCTAssertEqual(sut.exampleLocalLabel.text, error.message, "displayPerformUploadPost(with:) should set error as label if there is an error") +// } +// +// func testUnsuccessfulUploadPostShouldNotRouteToNext() { +// // given +// let spy = UploadPostRouterSpy() +// sut.router = spy +// loadView() +// let error = UploadPostModels.Error.init(type: .emptyExampleVariable) +// let viewModel = UploadPostModels.PerformUploadPost.ViewModel(error: error) +// +// // when +// sut.displayPerformUploadPost(with: viewModel) +// +// // then +// XCTAssertFalse(spy.routeToNextCalled, "displayPerformUploadPost(with:) should not route to next screen if there is an error") +// } +// +// func testSuccessfulUploadPostShouldRouteToNext() { +// // given +// let spy = UploadPostRouterSpy() +// sut.router = spy +// loadView() +// let viewModel = UploadPostModels.PerformUploadPost.ViewModel(error: nil) +// +// // when +// sut.displayPerformUploadPost(with: viewModel) +// +// // then +// XCTAssertTrue(spy.routeToNextCalled, "displayPerformUploadPost(with:) should route to next screen if there is no error") +// } +//} diff --git a/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostWorkerTests.swift b/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostWorkerTests.swift new file mode 100644 index 0000000..fc9b209 --- /dev/null +++ b/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostWorkerTests.swift @@ -0,0 +1,75 @@ +// +// UploadPostWorkerTests.swift +// LayoverTests +// +// Created by kong on 2023/12/12. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +@testable import Layover +import XCTest + +//class UploadPostWorkerTests: XCTestCase { +// +// // MARK: - Subject Under Test (SUT) +// +// typealias Models = UploadPostModels +// var sut: UploadPostWorker! +// +// // MARK: - Test Lifecycle +// +// override func setUp() { +// super.setUp() +// setupUploadPostWorker() +// } +// +// override func tearDown() { +// sut = nil +// super.tearDown() +// } +// +// // MARK: - Test Setup +// +// func setupUploadPostWorker() { +// sut = UploadPostWorker() +// } +// +// // MARK: - Test Doubles +// +// // MARK: - Tests +// +// func testValidateExampleVariableShouldCreateEmptyExampleVariableErrorIfExampleVariableIsNil() { +// // given +// let exampleVariable: String? = nil +// +// // when +// let error = sut.validate(exampleVariable: exampleVariable) +// +// // then +// XCTAssertNotNil(error, "validate(exampleVariable:) should create an error if example variable is nil") +// XCTAssertEqual(error?.type, Models.UploadPostErrorType.emptyExampleVariable, "validate(exampleVariable:) should create an emptyExampleVariable error if example variable is nil") +// } +// +// func testValidateExampleVariableShouldCreateEmptyExampleVariableErrorIfExampleVariableIsEmpty() { +// // given +// let exampleVariable = "" +// +// // when +// let error = sut.validate(exampleVariable: exampleVariable) +// +// // then +// XCTAssertNotNil(error, "validate(exampleVariable:) should create an error if example variable is empty") +// XCTAssertEqual(error?.type, Models.UploadPostErrorType.emptyExampleVariable, "validate(exampleVariable:) should create an emptyExampleVariable error if example variable is empty") +// } +// +// func testValidateExampleVariableShouldNotCreateErrorIfExampleVariableIsNotNilOrEmpty() { +// // given +// let exampleVariable = "Example string." +// +// // when +// let error = sut.validate(exampleVariable: exampleVariable) +// +// // then +// XCTAssertNil(error, "validate(exampleVariable:) should not create an error if example variable is not nil or empty") +// } +//} From fb1966c6b2ea2ebc9d42e98c71650f480cbcb407 Mon Sep 17 00:00:00 2001 From: kong <1018dbrud@gmail.com> Date: Wed, 13 Dec 2023 00:08:19 +0900 Subject: [PATCH 2/8] =?UTF-8?q?=E2=9C=85=20MockUploadPostWorker=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84,=20LocationManager=20=EC=B6=94=EC=83=81?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Layover/Layover.xcodeproj/project.pbxproj | 24 ++ .../UploadPost/UploadPostConfigurator.swift | 2 +- .../UploadPost/UploadPostInteractor.swift | 17 +- .../Location/CurrentLocationManager.swift | 57 +++++ .../Services/Location/LocationFetcher.swift | 39 +++ .../Mocks/MockDatas/PostBoard.json | 16 ++ .../Mocks/Workers/MockUploadPostWorker.swift | 43 ++++ .../UploadPostInteractorTests.swift | 239 ++++++++++++------ 8 files changed, 350 insertions(+), 87 deletions(-) create mode 100644 iOS/Layover/Layover/Services/Location/CurrentLocationManager.swift create mode 100644 iOS/Layover/Layover/Services/Location/LocationFetcher.swift create mode 100644 iOS/Layover/LayoverTests/Mocks/MockDatas/PostBoard.json create mode 100644 iOS/Layover/LayoverTests/Mocks/Workers/MockUploadPostWorker.swift diff --git a/iOS/Layover/Layover.xcodeproj/project.pbxproj b/iOS/Layover/Layover.xcodeproj/project.pbxproj index 7e253d4..62a37d8 100644 --- a/iOS/Layover/Layover.xcodeproj/project.pbxproj +++ b/iOS/Layover/Layover.xcodeproj/project.pbxproj @@ -165,6 +165,10 @@ FC4E0C0D2B282AE500152596 /* UploadPostInteractorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4E0C092B282AE500152596 /* UploadPostInteractorTests.swift */; }; FC4E0C0E2B282AE500152596 /* UploadPostWorkerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4E0C0A2B282AE500152596 /* UploadPostWorkerTests.swift */; }; FC4E0C0F2B282AE500152596 /* UploadPostPresenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4E0C0B2B282AE500152596 /* UploadPostPresenterTests.swift */; }; + FC4E0C112B28595200152596 /* MockUploadPostWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4E0C102B28595200152596 /* MockUploadPostWorker.swift */; }; + FC4E0C132B28609C00152596 /* PostBoard.json in Resources */ = {isa = PBXBuildFile; fileRef = FC4E0C122B28609C00152596 /* PostBoard.json */; }; + FC4E0C192B28955400152596 /* LocationFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4E0C182B28955400152596 /* LocationFetcher.swift */; }; + FC4E0C1D2B28977000152596 /* CurrentLocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4E0C1C2B28977000152596 /* CurrentLocationManager.swift */; }; FC5BE11C2B148D160036366D /* EditProfilePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5BE1162B148D160036366D /* EditProfilePresenter.swift */; }; FC5BE11D2B148D160036366D /* EditProfileWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5BE1172B148D160036366D /* EditProfileWorker.swift */; }; FC5BE11E2B148D160036366D /* EditProfileRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5BE1182B148D160036366D /* EditProfileRouter.swift */; }; @@ -386,6 +390,10 @@ FC4E0C092B282AE500152596 /* UploadPostInteractorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadPostInteractorTests.swift; sourceTree = ""; }; FC4E0C0A2B282AE500152596 /* UploadPostWorkerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadPostWorkerTests.swift; sourceTree = ""; }; FC4E0C0B2B282AE500152596 /* UploadPostPresenterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadPostPresenterTests.swift; sourceTree = ""; }; + FC4E0C102B28595200152596 /* MockUploadPostWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUploadPostWorker.swift; sourceTree = ""; }; + FC4E0C122B28609C00152596 /* PostBoard.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = PostBoard.json; sourceTree = ""; }; + FC4E0C182B28955400152596 /* LocationFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationFetcher.swift; sourceTree = ""; }; + FC4E0C1C2B28977000152596 /* CurrentLocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentLocationManager.swift; sourceTree = ""; }; FC5BE1162B148D160036366D /* EditProfilePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfilePresenter.swift; sourceTree = ""; }; FC5BE1172B148D160036366D /* EditProfileWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfileWorker.swift; sourceTree = ""; }; FC5BE1182B148D160036366D /* EditProfileRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfileRouter.swift; sourceTree = ""; }; @@ -535,6 +543,7 @@ isa = PBXGroup; children = ( 194C21CB2B1DF39200C62645 /* MockHomeWorker.swift */, + FC4E0C102B28595200152596 /* MockUploadPostWorker.swift */, ); path = Workers; sourceTree = ""; @@ -544,6 +553,7 @@ children = ( 194C21D32B1EEE3700C62645 /* sample.jpeg */, 194C21CF2B1DF65200C62645 /* PostList.json */, + FC4E0C122B28609C00152596 /* PostBoard.json */, ); path = MockDatas; sourceTree = ""; @@ -807,6 +817,15 @@ path = Fonts; sourceTree = ""; }; + FC4E0C172B28954000152596 /* Location */ = { + isa = PBXGroup; + children = ( + FC4E0C182B28955400152596 /* LocationFetcher.swift */, + FC4E0C1C2B28977000152596 /* CurrentLocationManager.swift */, + ); + path = Location; + sourceTree = ""; + }; FC5BE1112B148AF00036366D /* Views */ = { isa = PBXGroup; children = ( @@ -926,6 +945,7 @@ children = ( 1972CCD02B125E8800C3C762 /* UserDefaults */, 19C7AFD42B02583C003B35F2 /* Keychain */, + FC4E0C172B28954000152596 /* Location */, 1901D4A12B18F4BE00617A64 /* System.swift */, ); path = Services; @@ -1173,6 +1193,7 @@ buildActionMask = 2147483647; files = ( 194C21D02B1DF65200C62645 /* PostList.json in Resources */, + FC4E0C132B28609C00152596 /* PostBoard.json in Resources */, 194C21D42B1EEE3700C62645 /* sample.jpeg in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1328,7 +1349,9 @@ 1915D6E52B1FB82000CE1DD0 /* CheckSignUpDTO.swift in Sources */, 198167A02B20583D0032F563 /* SettingPresenter.swift in Sources */, 198167A42B20583D0032F563 /* SettingInteractor.swift in Sources */, + FC4E0C1D2B28977000152596 /* CurrentLocationManager.swift in Sources */, 83C35E1B2B108C3500D8DD5C /* PlaybackView.swift in Sources */, + FC4E0C192B28955400152596 /* LocationFetcher.swift in Sources */, 835A61A02B068115002F22A5 /* PlaybackModels.swift in Sources */, FC0E80292B1A0BBB00EF56D6 /* UploadPostInteractor.swift in Sources */, 836C33912B17629400ECAFB0 /* MapRouter.swift in Sources */, @@ -1393,6 +1416,7 @@ 194C21C32B1DEE6B00C62645 /* HomeViewControllerTests.swift in Sources */, 194C21CC2B1DF39200C62645 /* MockHomeWorker.swift in Sources */, FC4E0C0E2B282AE500152596 /* UploadPostWorkerTests.swift in Sources */, + FC4E0C112B28595200152596 /* MockUploadPostWorker.swift in Sources */, FC4E0C0D2B282AE500152596 /* UploadPostInteractorTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostConfigurator.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostConfigurator.swift index a31a522..fe655d2 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostConfigurator.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostConfigurator.swift @@ -18,7 +18,7 @@ final class UploadPostConfigurator: Configurator { func configure(_ viewController: ViewController) { let viewController = viewController - let interactor = UploadPostInteractor() + let interactor = UploadPostInteractor(locationManager: CurrentLocationManager()) let presenter = UploadPostPresenter() let router = UploadPostRouter() let worker = UploadPostWorker() diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift index 226cc63..35051e9 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift @@ -42,7 +42,7 @@ final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostD var presenter: UploadPostPresentationLogic? private let fileManager: FileManager - private let locationManager: CLLocationManager + private var locationManager: CurrentLocationManager // MARK: - Data Store @@ -53,7 +53,7 @@ final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostD // MARK: - Object LifeCycle init(fileManager: FileManager = FileManager.default, - locationManager: CLLocationManager = CLLocationManager()) { + locationManager: CurrentLocationManager) { self.fileManager = fileManager self.locationManager = locationManager } @@ -90,7 +90,7 @@ final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostD } func fetchCurrentAddress() { - guard let location = getCurrentLocation() else { return } + guard let location = locationManager.getCurrentLocation() else { return } let localeIdentifier = Locale.preferredLanguages.first != nil ? Locale.preferredLanguages[0] : Locale.current.identifier let locale = Locale(identifier: localeIdentifier) @@ -123,7 +123,7 @@ final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostD guard let worker, let videoURL, let isMuted, - let coordinate = getCurrentLocation()?.coordinate else { return false } + let coordinate = locationManager.getCurrentLocation()?.coordinate else { return false } if isMuted { exportVideoWithoutAudio(at: videoURL) } @@ -137,15 +137,6 @@ final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostD } } - private func getCurrentLocation() -> CLLocation? { - locationManager.desiredAccuracy = kCLLocationAccuracyBest - locationManager.startUpdatingLocation() - - guard let space = locationManager.location?.coordinate else { return nil } - let location = CLLocation(latitude: space.latitude, longitude: space.longitude) - return location - } - private func exportVideoWithoutAudio(at url: URL) { let composition = AVMutableComposition() let sourceAsset = AVURLAsset(url: url) diff --git a/iOS/Layover/Layover/Services/Location/CurrentLocationManager.swift b/iOS/Layover/Layover/Services/Location/CurrentLocationManager.swift new file mode 100644 index 0000000..3a4217e --- /dev/null +++ b/iOS/Layover/Layover/Services/Location/CurrentLocationManager.swift @@ -0,0 +1,57 @@ +// +// CurrentLocationManager.swift +// Layover +// +// Created by kong on 2023/12/12. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import CoreLocation + +class CurrentLocationManager: NSObject { + + typealias LocationCompletion = ((CLLocation) -> Void) + + private var locationFetcher: LocationFetcher + var currentLocationCompletion: LocationCompletion? + var authrizationCompletion: ((CLAuthorizationStatus) -> Void)? + + init(locationFetcher: LocationFetcher = CLLocationManager()) { + self.locationFetcher = locationFetcher + super.init() + self.locationFetcher.locationFetcherDelegate = self + self.locationFetcher.desiredAccuracy = kCLLocationAccuracyBest + } + + func getCurrentLocation() -> CLLocation? { + startUpdatingLocation() + + guard let space = locationFetcher.location?.coordinate else { return nil } + let location = CLLocation(latitude: space.latitude, longitude: space.longitude) + return location + } + + func startUpdatingLocation() { + self.locationFetcher.startUpdatingLocation() + } + + func locationFetcher(_ fetcher: LocationFetcher, didChangeAuthorization authorization: CLAuthorizationStatus) { + self.authrizationCompletion?(authorization) + self.authrizationCompletion = nil + } + +} + +extension CurrentLocationManager: LocationFetcherDelegate { + func locationFetcher(_ fetcher: LocationFetcher, didUpdateLocations locations: [CLLocation]) { + guard let location = locations.first else { return } + self.currentLocationCompletion?(location) + self.currentLocationCompletion = nil + } +} + +extension CurrentLocationManager: CLLocationManagerDelegate { + func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + self.locationFetcher(manager, didUpdateLocations: locations) + } +} diff --git a/iOS/Layover/Layover/Services/Location/LocationFetcher.swift b/iOS/Layover/Layover/Services/Location/LocationFetcher.swift new file mode 100644 index 0000000..057a71c --- /dev/null +++ b/iOS/Layover/Layover/Services/Location/LocationFetcher.swift @@ -0,0 +1,39 @@ +// +// LocationFetcher.swift +// Layover +// +// Created by kong on 2023/12/12. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import CoreLocation + +protocol LocationFetcherDelegate: AnyObject { + func locationFetcher(_ fetcher: LocationFetcher, didUpdateLocations locations: [CLLocation]) + func locationFetcher( + _ fetcher: LocationFetcher, + didChangeAuthorization authorization: CLAuthorizationStatus + ) +} + +protocol LocationFetcher { + var location: CLLocation? { get } + var locationFetcherDelegate: LocationFetcherDelegate? { get set } + var desiredAccuracy: CLLocationAccuracy { get set } + + func requestLocation() + func startUpdatingLocation() + func requestWhenInUseAuthorization() + func requestAlwaysAuthorization() +} + +extension CLLocationManager: LocationFetcher { + var locationFetcherDelegate: LocationFetcherDelegate? { + get { + return delegate as? LocationFetcherDelegate + } + set { + delegate = newValue as? CLLocationManagerDelegate + } + } +} diff --git a/iOS/Layover/LayoverTests/Mocks/MockDatas/PostBoard.json b/iOS/Layover/LayoverTests/Mocks/MockDatas/PostBoard.json new file mode 100644 index 0000000..c4611d0 --- /dev/null +++ b/iOS/Layover/LayoverTests/Mocks/MockDatas/PostBoard.json @@ -0,0 +1,16 @@ +{ + "message": "성공", + "statusCode": 200, + "data": { + "id": 1, + "title": "제목", + "content": "내용", + "latitude": 37.1231053, + "longitude": 127.1231053, + "tag": [ + "부산", + "광안리", + "바다" + ] + } +} diff --git a/iOS/Layover/LayoverTests/Mocks/Workers/MockUploadPostWorker.swift b/iOS/Layover/LayoverTests/Mocks/Workers/MockUploadPostWorker.swift new file mode 100644 index 0000000..de87a98 --- /dev/null +++ b/iOS/Layover/LayoverTests/Mocks/Workers/MockUploadPostWorker.swift @@ -0,0 +1,43 @@ +// +// MockUploadPostWorker.swift +// LayoverTests +// +// Created by kong on 2023/12/12. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import Foundation +import OSLog + +@testable import Layover + +final class MockUploadPostWorker: UploadPostWorkerProtocol { + + private let provider: ProviderType + + init(provider: ProviderType) { + self.provider = provider + } + + func uploadPost(with request: Layover.UploadPost) async -> Bool { + guard let fileLocation = Bundle(for: type(of: self)).url(forResource: "PostBoard", withExtension: "json") else { return false } + + do { + let endPoint: EndPoint> = EndPoint(path: "/board", + method: .POST, + bodyParameters: UploadPostRequestDTO(title: request.title, + content: request.content, + latitude: request.latitude, + longitude: request.longitude, + tag: request.tag)) + let response = try await provider.request(with: endPoint) + guard let data = response.data else { return false } + return true + } catch { + os_log(.error, log: .data, "Failed to fetch posts: %@", error.localizedDescription) + return false + } + + } + +} diff --git a/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostInteractorTests.swift b/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostInteractorTests.swift index ec40997..a89fe02 100644 --- a/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostInteractorTests.swift +++ b/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostInteractorTests.swift @@ -9,78 +9,171 @@ @testable import Layover import XCTest -//class UploadPostInteractorTests: XCTestCase { -// -// // MARK: - Subject Under Test (SUT) -// -// typealias Models = UploadPostModels -// var sut: UploadPostInteractor! -// -// // MARK: - Test Lifecycle -// -// override func setUp() { -// super.setUp() -// setupUploadPostInteractor() -// } -// -// override func tearDown() { -// sut = nil -// super.tearDown() -// } -// -// // MARK: - Test Setup -// -// func setupUploadPostInteractor() { -// sut = UploadPostInteractor() -// } -// -// // MARK: - Test Doubles -// -// class UploadPostPresentationLogicSpy: UploadPostPresentationLogic { -// -// // MARK: Spied Methods -// -// var presentFetchFromLocalDataStoreCalled = false -// var fetchFromLocalDataStoreResponse: UploadPostModels.FetchFromLocalDataStore.Response! -// func presentFetchFromLocalDataStore(with response: UploadPostModels.FetchFromLocalDataStore.Response) { -// presentFetchFromLocalDataStoreCalled = true -// fetchFromLocalDataStoreResponse = response -// } -// -// var presentFetchFromRemoteDataStoreCalled = false -// var fetchFromRemoteDataStoreResponse: UploadPostModels.FetchFromRemoteDataStore.Response! -// func presentFetchFromRemoteDataStore(with response: UploadPostModels.FetchFromRemoteDataStore.Response) { -// presentFetchFromRemoteDataStoreCalled = true -// fetchFromRemoteDataStoreResponse = response -// } -// -// var presentTrackAnalyticsCalled = false -// var trackAnalyticsResponse: UploadPostModels.TrackAnalytics.Response! -// func presentTrackAnalytics(with response: UploadPostModels.TrackAnalytics.Response) { -// presentTrackAnalyticsCalled = true -// trackAnalyticsResponse = response -// } -// -// var presentPerformUploadPostCalled = false -// var performUploadPostResponse: UploadPostModels.PerformUploadPost.Response! -// func presentPerformUploadPost(with response: UploadPostModels.PerformUploadPost.Response) { -// presentPerformUploadPostCalled = true -// performUploadPostResponse = response -// } -// } -// -// class UploadPostWorkerSpy: UploadPostWorker { -// -// // MARK: Spied Methods -// -// var validateExampleVariableCalled = false -// override func validate(exampleVariable: String?) -> Models.UploadPostError? { -// validateExampleVariableCalled = true -// return super.validate(exampleVariable: exampleVariable) -// } -// } -// -// // MARK: - Tests +class UploadPostInteractorTests: XCTestCase { + + // MARK: - Subject Under Test (SUT) + + typealias Models = UploadPostModels + var sut: UploadPostInteractor! + + // MARK: - Test Lifecycle + + override func setUp() { + super.setUp() + setupUploadPostInteractor() + } + + override func tearDown() { + sut = nil + super.tearDown() + } + + // MARK: - Test Setup + + func setupUploadPostInteractor() { + sut = UploadPostInteractor(locationManager: CurrentLocationManager()) + sut.worker = MockUploadPostWorker(provider: Provider(session: .initMockSession())) + } + + // MARK: - Test Doubles + + class UploadPostPresentationLogicSpy: UploadPostPresentationLogic { + + // MARK: Spied Methods + + var presentTagCalled = false + var presentTagsResponse: UploadPostModels.FetchTags.Response! + func presentTags(with response: Layover.UploadPostModels.FetchTags.Response) { + presentTagCalled = true + presentTagsResponse = response + } + + var presentThumbnailCalled = false + var presentThumbnailResponse: UploadPostModels.FetchThumbnail.Response! + func presentThumbnailImage(with response: Layover.UploadPostModels.FetchThumbnail.Response) { + presentThumbnailCalled = true + presentThumbnailResponse = response + } + + var presentCurrentAddressCalled = false + var presentCurrentAddressResponse: UploadPostModels.FetchCurrentAddress.Response! + func presentCurrentAddress(with response: Layover.UploadPostModels.FetchCurrentAddress.Response) { + presentCurrentAddressCalled = true + presentCurrentAddressResponse = response + } + + var presentUploadButtonCalled = false + var presentUploadButtonResponse: UploadPostModels.CanUploadPost.Response! + func presentUploadButton(with response: Layover.UploadPostModels.CanUploadPost.Response) { + presentUploadButtonCalled = true + presentUploadButtonResponse = response + } + + } + + // MARK: - Tests + + func test_fetchTags를_호출하면_presenter의_presentTags가_호출된다() { + // given + let spy = UploadPostPresentationLogicSpy() + sut.presenter = spy + + // when + sut.fetchTags() + + // then + XCTAssertTrue(spy.presentTagCalled, "") + } + + func test_fetchTags를_호출하면_datastore에_tags데이터가_전달된다() { + // given + let spy = UploadPostPresentationLogicSpy() + sut.presenter = spy + + // when + sut.fetchTags() + + // then + XCTAssertNotNil(sut.tags) + } + + func test_fetchCurrentAddress를_호출하면_presenter의_presentTags가_호출된다() { + // given + let spy = UploadPostPresentationLogicSpy() + sut.presenter = spy + + // when + sut.fetchCurrentAddress() + + // then + XCTAssertTrue(spy.presentCurrentAddressCalled, "") + } + + func test_fetchCurrentAddress를_호출하면_presenter에게_올바른데이터를_전달한다() { + // given + let spy = UploadPostPresentationLogicSpy() + sut.presenter = spy + + // when + sut.fetchCurrentAddress() + + // then +// XCTAssertTrue(spy.presentCurrentAddressResponse, "") + } + + func test_fetchThumbnailImage를_호출하면_presenter의_presentThumbnailImage가_호출된다() { + // given + let spy = UploadPostPresentationLogicSpy() + sut.presenter = spy + + // when + sut.fetchThumbnailImage() + + // then + XCTAssertTrue(spy.presentThumbnailCalled, "") + } + + func test_editTags를_호출하면_presenter의_presentThumbnailImage가_호출된다() { + // given + let spy = UploadPostPresentationLogicSpy() + sut.presenter = spy + let request = UploadPostModels.EditTags.Request(tags: []) + + // when + sut.editTags(with: request) + + // then + XCTAssertTrue(spy.presentThumbnailCalled, "") + } + + func test_canUploadPost를_호출하면_presenter의_presentThumbnailImage가_호출된다() { + // given + let spy = UploadPostPresentationLogicSpy() + sut.presenter = spy + let request = UploadPostModels.CanUploadPost.Request(title: <#T##String?#>) + + // when + sut.canUploadPost(request: request) + + // then + XCTAssertTrue(spy.presentThumbnailCalled, "") + } + + func test_uploadPost를_호출하면_presenter의_presentThumbnailImage가_호출된다() { + // given + let spy = UploadPostPresentationLogicSpy() + sut.presenter = spy + let request = UploadPostModels.UploadPost.Request(title: <#T##String#>, + content: <#T##String?#>, + tags: <#T##[String]#>) + + // when + sut.uploadPost(request: request) + + // then + XCTAssertTrue(spy.presentThumbnailCalled, "") + } + // // func testFetchFromLocalDataStoreShouldAskPresenterToFormat() { // // given @@ -136,4 +229,4 @@ import XCTest // // then // XCTAssertTrue(spy.presentPerformUploadPostCalled, "performUploadPost(with:) should ask the presenter to format the result") // } -//} +} From d2462cd6e137e881f1e9442537f19d049ddc6408 Mon Sep 17 00:00:00 2001 From: kong <1018dbrud@gmail.com> Date: Wed, 13 Dec 2023 03:02:32 +0900 Subject: [PATCH 3/8] =?UTF-8?q?=E2=9C=85=20MockLocationFetcher,=20UploadPo?= =?UTF-8?q?stInteractorTest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Layover/Layover.xcodeproj/project.pbxproj | 12 ++ .../UploadPost/UploadPostInteractor.swift | 54 ++++---- .../Scenes/UploadPost/UploadPostWorker.swift | 4 +- .../LocationFetcher/MockLocationFetcher.swift | 28 ++++ .../UploadPostInteractorTests.swift | 123 +++++++----------- .../UploadPost/UploadPostPresenterTests.swift | 16 +-- .../UploadPost/UploadPostWorkerTests.swift | 90 ++++--------- 7 files changed, 146 insertions(+), 181 deletions(-) create mode 100644 iOS/Layover/LayoverTests/Mocks/LocationFetcher/MockLocationFetcher.swift diff --git a/iOS/Layover/Layover.xcodeproj/project.pbxproj b/iOS/Layover/Layover.xcodeproj/project.pbxproj index 62a37d8..e06fdf0 100644 --- a/iOS/Layover/Layover.xcodeproj/project.pbxproj +++ b/iOS/Layover/Layover.xcodeproj/project.pbxproj @@ -169,6 +169,7 @@ FC4E0C132B28609C00152596 /* PostBoard.json in Resources */ = {isa = PBXBuildFile; fileRef = FC4E0C122B28609C00152596 /* PostBoard.json */; }; FC4E0C192B28955400152596 /* LocationFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4E0C182B28955400152596 /* LocationFetcher.swift */; }; FC4E0C1D2B28977000152596 /* CurrentLocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4E0C1C2B28977000152596 /* CurrentLocationManager.swift */; }; + FC4E0C202B28B4C500152596 /* MockLocationFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4E0C1F2B28B4C500152596 /* MockLocationFetcher.swift */; }; FC5BE11C2B148D160036366D /* EditProfilePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5BE1162B148D160036366D /* EditProfilePresenter.swift */; }; FC5BE11D2B148D160036366D /* EditProfileWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5BE1172B148D160036366D /* EditProfileWorker.swift */; }; FC5BE11E2B148D160036366D /* EditProfileRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5BE1182B148D160036366D /* EditProfileRouter.swift */; }; @@ -394,6 +395,7 @@ FC4E0C122B28609C00152596 /* PostBoard.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = PostBoard.json; sourceTree = ""; }; FC4E0C182B28955400152596 /* LocationFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationFetcher.swift; sourceTree = ""; }; FC4E0C1C2B28977000152596 /* CurrentLocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentLocationManager.swift; sourceTree = ""; }; + FC4E0C1F2B28B4C500152596 /* MockLocationFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockLocationFetcher.swift; sourceTree = ""; }; FC5BE1162B148D160036366D /* EditProfilePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfilePresenter.swift; sourceTree = ""; }; FC5BE1172B148D160036366D /* EditProfileWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfileWorker.swift; sourceTree = ""; }; FC5BE1182B148D160036366D /* EditProfileRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfileRouter.swift; sourceTree = ""; }; @@ -535,6 +537,7 @@ children = ( 194C21CE2B1DF63D00C62645 /* MockDatas */, 194C21CA2B1DF37900C62645 /* Workers */, + FC4E0C1E2B28B4AD00152596 /* LocationFetcher */, ); path = Mocks; sourceTree = ""; @@ -826,6 +829,14 @@ path = Location; sourceTree = ""; }; + FC4E0C1E2B28B4AD00152596 /* LocationFetcher */ = { + isa = PBXGroup; + children = ( + FC4E0C1F2B28B4C500152596 /* MockLocationFetcher.swift */, + ); + path = LocationFetcher; + sourceTree = ""; + }; FC5BE1112B148AF00036366D /* Views */ = { isa = PBXGroup; children = ( @@ -1413,6 +1424,7 @@ FC4E0C0C2B282AE500152596 /* UploadPostViewControllerTests.swift in Sources */, 194C21C52B1DEE6B00C62645 /* HomeWorkerTests.swift in Sources */, 194C21C62B1DEE6B00C62645 /* HomePresenterTests.swift in Sources */, + FC4E0C202B28B4C500152596 /* MockLocationFetcher.swift in Sources */, 194C21C32B1DEE6B00C62645 /* HomeViewControllerTests.swift in Sources */, 194C21CC2B1DF39200C62645 /* MockHomeWorker.swift in Sources */, FC4E0C0E2B282AE500152596 /* UploadPostWorkerTests.swift in Sources */, diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift index 35051e9..c596681 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift @@ -15,15 +15,10 @@ import OSLog protocol UploadPostBusinessLogic { func fetchTags() func editTags(with request: UploadPostModels.EditTags.Request) - - @discardableResult - func fetchThumbnailImage() -> Task - + func fetchThumbnailImage() func fetchCurrentAddress() func canUploadPost(request: UploadPostModels.CanUploadPost.Request) - - @discardableResult - func uploadPost(request: UploadPostModels.UploadPost.Request) -> Task + func uploadPost(request: UploadPostModels.UploadPost.Request) } protocol UploadPostDataStore { @@ -53,7 +48,7 @@ final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostD // MARK: - Object LifeCycle init(fileManager: FileManager = FileManager.default, - locationManager: CurrentLocationManager) { + locationManager: CurrentLocationManager = CurrentLocationManager()) { self.fileManager = fileManager self.locationManager = locationManager } @@ -69,22 +64,19 @@ final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostD tags = request.tags } - @discardableResult - func fetchThumbnailImage() -> Task { - Task { - guard let videoURL else { return false } + func fetchThumbnailImage() { + guard let videoURL else { return } let asset = AVAsset(url: videoURL) let generator = AVAssetImageGenerator(asset: asset) generator.appliesPreferredTrackTransform = true + Task { do { let image = try await generator.image(at: .zero).image await MainActor.run { presenter?.presentThumbnailImage(with: Models.FetchThumbnail.Response(thumbnailImage: image)) } - return true } catch let error { - os_log(.error, log: .default, "Failed to fetch Thumbnail Image with error: %@", error.localizedDescription) - return false + os_log(.error, log: .data, "Failed to fetch Thumbnail Image with error: %@", error.localizedDescription) } } } @@ -107,7 +99,7 @@ final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostD presenter?.presentCurrentAddress(with: response) } } catch { - os_log(.error, log: .default, "Failed to fetch Current Address with error: %@", error.localizedDescription) + os_log(.error, log: .data, "Failed to fetch Current Address with error: %@", error.localizedDescription) } } } @@ -117,23 +109,21 @@ final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostD presenter?.presentUploadButton(with: response) } - @discardableResult - func uploadPost(request: UploadPostModels.UploadPost.Request) -> Task { + func uploadPost(request: UploadPostModels.UploadPost.Request) { + guard let worker, + let videoURL, + let isMuted, + let coordinate = locationManager.getCurrentLocation()?.coordinate else { return } + if isMuted { + exportVideoWithoutAudio(at: videoURL) + } Task { - guard let worker, - let videoURL, - let isMuted, - let coordinate = locationManager.getCurrentLocation()?.coordinate else { return false } - if isMuted { - exportVideoWithoutAudio(at: videoURL) - } - let response = await worker.uploadPost(with: UploadPost(title: request.title, - content: request.content, - latitude: coordinate.latitude, - longitude: coordinate.longitude, - tag: request.tags, - videoURL: videoURL)) - return response + _ = await worker.uploadPost(with: UploadPost(title: request.title, + content: request.content, + latitude: coordinate.latitude, + longitude: coordinate.longitude, + tag: request.tags, + videoURL: videoURL)) } } diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift index 8d91918..5dba269 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift @@ -56,8 +56,8 @@ final class UploadPostWorker: NSObject, UploadPostWorkerProtocol { let response = try await provider.request(with: endPoint) guard let preSignedURLString = response.data?.preSignedURL else { return false } _ = try await provider.upload(fromFile: videoURL, - to: preSignedURLString, - sessionTaskDelegate: self) + to: preSignedURLString, + sessionTaskDelegate: self) NotificationCenter.default.post(name: .uploadTaskDidComplete, object: nil) return true } catch { diff --git a/iOS/Layover/LayoverTests/Mocks/LocationFetcher/MockLocationFetcher.swift b/iOS/Layover/LayoverTests/Mocks/LocationFetcher/MockLocationFetcher.swift new file mode 100644 index 0000000..c505d3c --- /dev/null +++ b/iOS/Layover/LayoverTests/Mocks/LocationFetcher/MockLocationFetcher.swift @@ -0,0 +1,28 @@ +// +// MockLocationFetcher.swift +// LayoverTests +// +// Created by kong on 2023/12/13. +// Copyright © 2023 CodeBomber. All rights reserved. +// + +import CoreLocation + +@testable import Layover + +final class MockLocationFetcher: LocationFetcher { + var location: CLLocation? = CLLocation(latitude: 37.7749, longitude: 122.4194) + var locationFetcherDelegate: Layover.LocationFetcherDelegate? + var desiredAccuracy: CLLocationAccuracy = kCLLocationAccuracyBest + + func requestLocation() { } + + func startUpdatingLocation() { + guard let location else { return } + locationFetcherDelegate?.locationFetcher(self, didUpdateLocations: [location]) + } + + func requestWhenInUseAuthorization() { } + + func requestAlwaysAuthorization() { } +} diff --git a/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostInteractorTests.swift b/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostInteractorTests.swift index a89fe02..d7ccd26 100644 --- a/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostInteractorTests.swift +++ b/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostInteractorTests.swift @@ -31,7 +31,7 @@ class UploadPostInteractorTests: XCTestCase { // MARK: - Test Setup func setupUploadPostInteractor() { - sut = UploadPostInteractor(locationManager: CurrentLocationManager()) + sut = UploadPostInteractor(locationManager: CurrentLocationManager(locationFetcher: MockLocationFetcher())) sut.worker = MockUploadPostWorker(provider: Provider(session: .initMockSession())) } @@ -82,7 +82,7 @@ class UploadPostInteractorTests: XCTestCase { sut.fetchTags() // then - XCTAssertTrue(spy.presentTagCalled, "") + XCTAssertTrue(spy.presentTagCalled, "fetchTags 함수가 presentTags을 호출하지 못함") } func test_fetchTags를_호출하면_datastore에_tags데이터가_전달된다() { @@ -94,46 +94,50 @@ class UploadPostInteractorTests: XCTestCase { sut.fetchTags() // then - XCTAssertNotNil(sut.tags) + XCTAssertNotNil(sut.tags, "fetchTags 함수가 datastore에 tags 데이터를 전달하지 못함") } - func test_fetchCurrentAddress를_호출하면_presenter의_presentTags가_호출된다() { + func test_fetchCurrentAddress를_호출하면_presenter의_presentCurrentAddress가_호출된다() async throws { // given let spy = UploadPostPresentationLogicSpy() sut.presenter = spy // when sut.fetchCurrentAddress() + try await Task.sleep(nanoseconds: 3_000_000_000) // then - XCTAssertTrue(spy.presentCurrentAddressCalled, "") + XCTAssertTrue(spy.presentCurrentAddressCalled, "fetchCurrentAddress 함수가 presentCurrentAddress을 호출하지 못함") } - func test_fetchCurrentAddress를_호출하면_presenter에게_올바른데이터를_전달한다() { + func test_fetchCurrentAddress를_호출하면_presenter에게_위치데이터를_전달한다() async throws { // given let spy = UploadPostPresentationLogicSpy() sut.presenter = spy // when sut.fetchCurrentAddress() + try await Task.sleep(nanoseconds: 3_000_000_000) // then -// XCTAssertTrue(spy.presentCurrentAddressResponse, "") + XCTAssertNotNil(spy.presentCurrentAddressResponse, "fetchCurrentAddress 함수가 presenter에게 위치데이터를 전달하지 못함") } - func test_fetchThumbnailImage를_호출하면_presenter의_presentThumbnailImage가_호출된다() { + func test_fetchThumbnailImage를_호출하면_presenter의_presentThumbnailImage가_호출된다() async throws { // given let spy = UploadPostPresentationLogicSpy() sut.presenter = spy + sut.videoURL = URL(string: "https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8") // when sut.fetchThumbnailImage() + try await Task.sleep(nanoseconds: 3_000_000_000) // then - XCTAssertTrue(spy.presentThumbnailCalled, "") + XCTAssertTrue(spy.presentThumbnailCalled, "fetchThumbnailImage 함수가 presentThumbnailImage을 호출하지 못함") } - func test_editTags를_호출하면_presenter의_presentThumbnailImage가_호출된다() { + func test_editTags를_호출하면_datastore에_tags데이터가_전달된다() { // given let spy = UploadPostPresentationLogicSpy() sut.presenter = spy @@ -143,90 +147,59 @@ class UploadPostInteractorTests: XCTestCase { sut.editTags(with: request) // then - XCTAssertTrue(spy.presentThumbnailCalled, "") + XCTAssertNotNil(sut.tags, "editTags 함수가 datastore의 tags 데이터를 전달하지 못함") } - func test_canUploadPost를_호출하면_presenter의_presentThumbnailImage가_호출된다() { + func test_title이_nil일때_canUploadPost를_호출하면_presenter의_presentUploadButtonCalled가_호출된다() { // given let spy = UploadPostPresentationLogicSpy() sut.presenter = spy - let request = UploadPostModels.CanUploadPost.Request(title: <#T##String?#>) + let request = UploadPostModels.CanUploadPost.Request(title: nil) // when sut.canUploadPost(request: request) // then - XCTAssertTrue(spy.presentThumbnailCalled, "") + XCTAssertTrue(spy.presentUploadButtonCalled, "title이 nil일 때 canUploadPost 함수가 presentUploadButton을 호출하지 못함") } - func test_uploadPost를_호출하면_presenter의_presentThumbnailImage가_호출된다() { + func test_title이_nil이아닐때_canUploadPost를_호출하면_presenter의_presentUploadButtonCalled가_호출된다() { // given let spy = UploadPostPresentationLogicSpy() sut.presenter = spy - let request = UploadPostModels.UploadPost.Request(title: <#T##String#>, - content: <#T##String?#>, - tags: <#T##[String]#>) + let request = UploadPostModels.CanUploadPost.Request(title: "아아아아") // when - sut.uploadPost(request: request) + sut.canUploadPost(request: request) // then - XCTAssertTrue(spy.presentThumbnailCalled, "") + XCTAssertTrue(spy.presentUploadButtonCalled, "title이 nil이 아닐 때 canUploadPost 함수가 presentUploadButton을 호출하지 못함") + } + + func test_title이_nil일때_canUploadPost를_호출하면_올바른결과가_presenter에_전달된다() { + // given + let spy = UploadPostPresentationLogicSpy() + sut.presenter = spy + let request = UploadPostModels.CanUploadPost.Request(title: nil) + + // when + sut.canUploadPost(request: request) + + // then + XCTAssertEqual(spy.presentUploadButtonResponse.isEmpty, true, "title이 nil일 때 canUploadPost 함수가 올바른 결과를 전달하지 못함") + } + + func test_title이_nil이아닐때_canUploadPost를_호출하면_올바른결과가_presenter에_전달된다() { + // given + let spy = UploadPostPresentationLogicSpy() + sut.presenter = spy + let request = UploadPostModels.CanUploadPost.Request(title: "아아아아") + + // when + sut.canUploadPost(request: request) + + // then + XCTAssertEqual(spy.presentUploadButtonResponse.isEmpty, false, "title이 nil이 아닐 때 canUploadPost 함수가 올바른 결과를 전달하지 못함") } -// -// func testFetchFromLocalDataStoreShouldAskPresenterToFormat() { -// // given -// let spy = UploadPostPresentationLogicSpy() -// sut.presenter = spy -// let request = Models.FetchFromLocalDataStore.Request() -// -// // when -// sut.fetchFromLocalDataStore(with: request) -// -// // then -// XCTAssertTrue(spy.presentFetchFromLocalDataStoreCalled, "fetchFromLocalDataStore(with:) should ask the presenter to format the result") -// } -// -// func testTrackAnalyticsShouldAskPresenterToFormat() { -// // given -// let spy = UploadPostPresentationLogicSpy() -// sut.presenter = spy -// let request = Models.TrackAnalytics.Request(event: .screenView) -// -// // when -// sut.trackAnalytics(with: request) -// -// // then -// XCTAssertTrue(spy.presentTrackAnalyticsCalled, "trackAnalytics(with:) should ask the presenter to format the result") -// } -// -// func testPerformUploadPostShouldValidateExampleVariable() { -// // given -// let spy = UploadPostWorkerSpy() -// sut.worker = spy -// let request = Models.PerformUploadPost.Request() -// -// // when -// sut.performUploadPost(with: request) -// -// // then -// XCTAssertTrue(spy.validateExampleVariableCalled, "performUploadPost(with:) should ask the worker to validate the example variable") -// } -// -// func testPerformUploadPostShouldAskPresenterToFormat() { -// // given -// let spy = UploadPostPresentationLogicSpy() -// sut.presenter = spy -// let request = Models.PerformUploadPost.Request() -// -// // when -// let expect = expectation(description: "Wait for performUploadPost(with:) to return") -// sut.performUploadPost(with: request) -// expect.fulfill() -// waitForExpectations(timeout: 1) -// -// // then -// XCTAssertTrue(spy.presentPerformUploadPostCalled, "performUploadPost(with:) should ask the presenter to format the result") -// } } diff --git a/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostPresenterTests.swift b/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostPresenterTests.swift index bf34e1f..99cef62 100644 --- a/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostPresenterTests.swift +++ b/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostPresenterTests.swift @@ -73,7 +73,7 @@ class UploadPostPresenterTests: XCTestCase { // MARK: - Tests - func test_presentTags를_실행하면_displayTags를_실행한다() { + func test_presentTags를_호출하면_displayTags를_호출한다() { // given let spy = UploadPostDisplayLogicSpy() sut.viewController = spy @@ -83,10 +83,10 @@ class UploadPostPresenterTests: XCTestCase { sut.presentTags(with: response) // then - XCTAssertTrue(spy.displayTagsCalled, "presentTags(with:) should ask the view controller to display the result") + XCTAssertTrue(spy.displayTagsCalled, "presentTags(with:) 함수가 displayTags를 호출하지 못함") } - func test_presentThumbnail을_실행하면_displayThumbnail을_실행한다() { + func test_presentThumbnail을_호출하면_displayThumbnail을_호출한다() { // given let spy = UploadPostDisplayLogicSpy() sut.viewController = spy @@ -97,10 +97,10 @@ class UploadPostPresenterTests: XCTestCase { sut.presentThumbnailImage(with: response) // then - XCTAssertTrue(spy.displayThumbnailCalled, "presentThumbnailImage(with:) should ask the view controller to display the result") + XCTAssertTrue(spy.displayThumbnailCalled, "presentThumbnailImage(with:) 함수가 displayThumbnail을 호출하지 못함") } - func test_presentCurrentAddress를_실행하면_displayCurrentAddress를_실행한다() { + func test_presentCurrentAddress를_호출하면_displayCurrentAddress를_호출한다() { // given let spy = UploadPostDisplayLogicSpy() sut.viewController = spy @@ -112,10 +112,10 @@ class UploadPostPresenterTests: XCTestCase { sut.presentCurrentAddress(with: response) // then - XCTAssertTrue(spy.displayCurrentAddressCalled, "presentCurrentAddress(with:) should ask the view controller to display the result") + XCTAssertTrue(spy.displayCurrentAddressCalled, "presentCurrentAddress(with:) 함수가 displayCurrentAddress을 호출하지 못함") } - func test_presentUploadButton를_실행하면_displayUploadButton을_실행한다() { + func test_presentUploadButton를_호출하면_displayUploadButton을_호출한다() { // given let spy = UploadPostDisplayLogicSpy() sut.viewController = spy @@ -125,7 +125,7 @@ class UploadPostPresenterTests: XCTestCase { sut.presentUploadButton(with: response) // then - XCTAssertTrue(spy.displayUploadButtonCalled, "presentUploadButton(with:) should ask the view controller to display the result") + XCTAssertTrue(spy.displayUploadButtonCalled, "presentUploadButton(with:) 함수가 displayUploadButton을 호출하지 못함") } } diff --git a/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostWorkerTests.swift b/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostWorkerTests.swift index fc9b209..046e0e7 100644 --- a/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostWorkerTests.swift +++ b/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostWorkerTests.swift @@ -9,67 +9,29 @@ @testable import Layover import XCTest -//class UploadPostWorkerTests: XCTestCase { -// -// // MARK: - Subject Under Test (SUT) -// -// typealias Models = UploadPostModels -// var sut: UploadPostWorker! -// -// // MARK: - Test Lifecycle -// -// override func setUp() { -// super.setUp() -// setupUploadPostWorker() -// } -// -// override func tearDown() { -// sut = nil -// super.tearDown() -// } -// -// // MARK: - Test Setup -// -// func setupUploadPostWorker() { -// sut = UploadPostWorker() -// } -// -// // MARK: - Test Doubles -// -// // MARK: - Tests -// -// func testValidateExampleVariableShouldCreateEmptyExampleVariableErrorIfExampleVariableIsNil() { -// // given -// let exampleVariable: String? = nil -// -// // when -// let error = sut.validate(exampleVariable: exampleVariable) -// -// // then -// XCTAssertNotNil(error, "validate(exampleVariable:) should create an error if example variable is nil") -// XCTAssertEqual(error?.type, Models.UploadPostErrorType.emptyExampleVariable, "validate(exampleVariable:) should create an emptyExampleVariable error if example variable is nil") -// } -// -// func testValidateExampleVariableShouldCreateEmptyExampleVariableErrorIfExampleVariableIsEmpty() { -// // given -// let exampleVariable = "" -// -// // when -// let error = sut.validate(exampleVariable: exampleVariable) -// -// // then -// XCTAssertNotNil(error, "validate(exampleVariable:) should create an error if example variable is empty") -// XCTAssertEqual(error?.type, Models.UploadPostErrorType.emptyExampleVariable, "validate(exampleVariable:) should create an emptyExampleVariable error if example variable is empty") -// } -// -// func testValidateExampleVariableShouldNotCreateErrorIfExampleVariableIsNotNilOrEmpty() { -// // given -// let exampleVariable = "Example string." -// -// // when -// let error = sut.validate(exampleVariable: exampleVariable) -// -// // then -// XCTAssertNil(error, "validate(exampleVariable:) should not create an error if example variable is not nil or empty") -// } -//} +class UploadPostWorkerTests: XCTestCase { + + // MARK: - Subject Under Test (SUT) + + typealias Models = UploadPostModels + var sut: UploadPostWorker! + + // MARK: - Test Lifecycle + + override func setUp() { + super.setUp() + setupUploadPostWorker() + } + + override func tearDown() { + sut = nil + super.tearDown() + } + + // MARK: - Test Setup + + func setupUploadPostWorker() { + sut = UploadPostWorker() + } + +} From 804dd5296e640fd658a866975e47c4c7c15a616c Mon Sep 17 00:00:00 2001 From: kong <1018dbrud@gmail.com> Date: Wed, 13 Dec 2023 04:11:07 +0900 Subject: [PATCH 4/8] =?UTF-8?q?=E2=9C=85=20UploadPostWorkerTests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UploadPost/UploadPostInteractor.swift | 6 +- .../Scenes/UploadPost/UploadPostWorker.swift | 15 ++-- .../Mocks/Workers/MockUploadPostWorker.swift | 24 +++++-- .../UploadPostInteractorTests.swift | 2 +- .../UploadPostViewControllerTests.swift | 70 +++++++++---------- .../UploadPost/UploadPostWorkerTests.swift | 34 ++++++++- 6 files changed, 98 insertions(+), 53 deletions(-) diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift index c596681..f8fe0b8 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift @@ -118,12 +118,16 @@ final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostD exportVideoWithoutAudio(at: videoURL) } Task { - _ = await worker.uploadPost(with: UploadPost(title: request.title, + let uploadPostResponse = await worker.uploadPost(with: UploadPost(title: request.title, content: request.content, latitude: coordinate.latitude, longitude: coordinate.longitude, tag: request.tags, videoURL: videoURL)) + guard let boardID = uploadPostResponse?.id else { return } + let fileType = videoURL.pathExtension + _ = await worker.uploadVideo(with: UploadVideoRequestDTO(boardID: boardID, filetype: fileType), + videoURL: videoURL) } } diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift index 5dba269..02535f0 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostWorker.swift @@ -11,7 +11,8 @@ import UIKit import OSLog protocol UploadPostWorkerProtocol { - func uploadPost(with request: UploadPost) async -> Bool + func uploadPost(with request: UploadPost) async -> UploadPostDTO? + func uploadVideo(with request: UploadVideoRequestDTO, videoURL: URL) async -> Bool } final class UploadPostWorker: NSObject, UploadPostWorkerProtocol { @@ -30,7 +31,7 @@ final class UploadPostWorker: NSObject, UploadPostWorkerProtocol { self.uploadPostEndPointFactory = uploadPostEndPointFactory } - func uploadPost(with request: UploadPost) async -> Bool { + func uploadPost(with request: UploadPost) async -> UploadPostDTO? { let endPoint = uploadPostEndPointFactory.makeUploadPostEndPoint(title: request.title, content: request.content, latitude: request.latitude, @@ -38,18 +39,14 @@ final class UploadPostWorker: NSObject, UploadPostWorkerProtocol { tag: request.tag) do { let response = try await provider.request(with: endPoint) - guard let boardID = response.data?.id else { return false } - let fileType = request.videoURL.pathExtension - let uploadResponse = await uploadVideo(with: UploadVideoRequestDTO(boardID: boardID, filetype: fileType), - videoURL: request.videoURL) - return uploadResponse + return response.data } catch { os_log(.error, log: .data, "Failed to fetch posts: %@", error.localizedDescription) - return false + return nil } } - private func uploadVideo(with request: UploadVideoRequestDTO, videoURL: URL) async -> Bool { + func uploadVideo(with request: UploadVideoRequestDTO, videoURL: URL) async -> Bool { let endPoint = uploadPostEndPointFactory.makeUploadVideoEndPoint(boardID: request.boardID, fileType: request.filetype) do { diff --git a/iOS/Layover/LayoverTests/Mocks/Workers/MockUploadPostWorker.swift b/iOS/Layover/LayoverTests/Mocks/Workers/MockUploadPostWorker.swift index de87a98..857fbd6 100644 --- a/iOS/Layover/LayoverTests/Mocks/Workers/MockUploadPostWorker.swift +++ b/iOS/Layover/LayoverTests/Mocks/Workers/MockUploadPostWorker.swift @@ -19,9 +19,9 @@ final class MockUploadPostWorker: UploadPostWorkerProtocol { self.provider = provider } - func uploadPost(with request: Layover.UploadPost) async -> Bool { - guard let fileLocation = Bundle(for: type(of: self)).url(forResource: "PostBoard", withExtension: "json") else { return false } - + func uploadPost(with request: UploadPost) async -> UploadPostDTO? { + guard let fileLocation = Bundle(for: type(of: self)).url(forResource: "PostBoard", + withExtension: "json") else { return nil } do { let endPoint: EndPoint> = EndPoint(path: "/board", method: .POST, @@ -30,14 +30,26 @@ final class MockUploadPostWorker: UploadPostWorkerProtocol { latitude: request.latitude, longitude: request.longitude, tag: request.tag)) + let mockData = try Data(contentsOf: fileLocation) + MockURLProtocol.requestHandler = { request in + let response = HTTPURLResponse(url: request.url!, + statusCode: 200, + httpVersion: nil, + headerFields: nil) + return (response, mockData, nil) + } let response = try await provider.request(with: endPoint) - guard let data = response.data else { return false } - return true + guard let data = response.data else { return nil } + return data } catch { os_log(.error, log: .data, "Failed to fetch posts: %@", error.localizedDescription) - return false + return nil } } + func uploadVideo(with request: UploadVideoRequestDTO, videoURL: URL) async -> Bool { + return true + } + } diff --git a/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostInteractorTests.swift b/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostInteractorTests.swift index d7ccd26..26194ae 100644 --- a/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostInteractorTests.swift +++ b/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostInteractorTests.swift @@ -131,7 +131,7 @@ class UploadPostInteractorTests: XCTestCase { // when sut.fetchThumbnailImage() - try await Task.sleep(nanoseconds: 3_000_000_000) + try await Task.sleep(nanoseconds: 5_000_000_000) // then XCTAssertTrue(spy.presentThumbnailCalled, "fetchThumbnailImage 함수가 presentThumbnailImage을 호출하지 못함") diff --git a/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostViewControllerTests.swift b/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostViewControllerTests.swift index 190a498..3cd99c9 100644 --- a/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostViewControllerTests.swift +++ b/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostViewControllerTests.swift @@ -9,40 +9,40 @@ @testable import Layover import XCTest -//class UploadPostViewControllerTests: XCTestCase { -// -// // MARK: - Subject Under Test (SUT) -// -// typealias Models = UploadPostModels -// var sut: UploadPostViewController! -// var window: UIWindow! -// -// // MARK: - Test Lifecycle -// -// override func setUp() { -// super.setUp() -// window = UIWindow() -// setupUploadPostViewController() -// } -// -// override func tearDown() { -// window = nil -// sut = nil -// super.tearDown() -// } -// -// // MARK: - Test Setup -// -// func setupUploadPostViewController() { -// let bundle = Bundle.main -// let storyboard = UIStoryboard(name: "Main", bundle: bundle) -// sut = storyboard.instantiateViewController(withIdentifier: "UploadPostViewController") as? UploadPostViewController -// } -// -// func loadView() { -// window.addSubview(sut.view) -// RunLoop.current.run(until: Date()) -// } +class UploadPostViewControllerTests: XCTestCase { + + // MARK: - Subject Under Test (SUT) + + typealias Models = UploadPostModels + var sut: UploadPostViewController! + var window: UIWindow! + + // MARK: - Test Lifecycle + + override func setUp() { + super.setUp() + window = UIWindow() + setupUploadPostViewController() + } + + override func tearDown() { + window = nil + sut = nil + super.tearDown() + } + + // MARK: - Test Setup + + func setupUploadPostViewController() { + let bundle = Bundle.main + let storyboard = UIStoryboard(name: "Main", bundle: bundle) + sut = storyboard.instantiateViewController(withIdentifier: "UploadPostViewController") as? UploadPostViewController + } + + func loadView() { + window.addSubview(sut.view) + RunLoop.current.run(until: Date()) + } // // // MARK: - Test Doubles // @@ -200,4 +200,4 @@ import XCTest // // then // XCTAssertTrue(spy.routeToNextCalled, "displayPerformUploadPost(with:) should route to next screen if there is no error") // } -//} +} diff --git a/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostWorkerTests.swift b/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostWorkerTests.swift index 046e0e7..f7f6140 100644 --- a/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostWorkerTests.swift +++ b/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostWorkerTests.swift @@ -31,7 +31,39 @@ class UploadPostWorkerTests: XCTestCase { // MARK: - Test Setup func setupUploadPostWorker() { - sut = UploadPostWorker() + sut = UploadPostWorker(provider: Provider(session: .initMockSession(), authManager: StubAuthManager())) + } + + func test_uploadPost는_업로드에_성공하면_올바른결과를_반환한다() async throws { + // given + guard let mockFileLocation = Bundle(for: type(of: self)).url(forResource: "PostBoard", withExtension: "json"), + let mockData = try? Data(contentsOf: mockFileLocation) else { + XCTFail("Mock json 파일 로드 실패.") + return + } + + MockURLProtocol.requestHandler = { request in + let response = HTTPURLResponse(url: request.url!, + statusCode: 200, + httpVersion: nil, + headerFields: nil) + return (response, mockData, nil) + } + + let request = UploadPost(title: "제목", + content: nil, + latitude: 100, + longitude: 100, + tag: [], + videoURL: URL(string: "https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8")!) + + // when + let response = await sut.uploadPost(with: request) + try await Task.sleep(nanoseconds: 3_000_000_000) + + // then + XCTAssertNotNil(response, "uploadPost가 response를 정상적으로 반환하지 못함") + XCTAssertEqual(response?.id, 1) } } From 3004dd1f2713326728f0dea6b147bcff01308410 Mon Sep 17 00:00:00 2001 From: kong <1018dbrud@gmail.com> Date: Wed, 13 Dec 2023 15:05:50 +0900 Subject: [PATCH 5/8] =?UTF-8?q?=F0=9F=94=A7=20didChangeAuthorization=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Scenes/UploadPost/UploadPostInteractor.swift | 2 +- .../Services/Location/CurrentLocationManager.swift | 14 +++++++++----- .../Services/Location/LocationFetcher.swift | 5 +---- .../LocationFetcher/MockLocationFetcher.swift | 4 +++- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift index f8fe0b8..fd5073c 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift @@ -37,7 +37,7 @@ final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostD var presenter: UploadPostPresentationLogic? private let fileManager: FileManager - private var locationManager: CurrentLocationManager + private let locationManager: CurrentLocationManager // MARK: - Data Store diff --git a/iOS/Layover/Layover/Services/Location/CurrentLocationManager.swift b/iOS/Layover/Layover/Services/Location/CurrentLocationManager.swift index 3a4217e..38f8c38 100644 --- a/iOS/Layover/Layover/Services/Location/CurrentLocationManager.swift +++ b/iOS/Layover/Layover/Services/Location/CurrentLocationManager.swift @@ -35,11 +35,6 @@ class CurrentLocationManager: NSObject { self.locationFetcher.startUpdatingLocation() } - func locationFetcher(_ fetcher: LocationFetcher, didChangeAuthorization authorization: CLAuthorizationStatus) { - self.authrizationCompletion?(authorization) - self.authrizationCompletion = nil - } - } extension CurrentLocationManager: LocationFetcherDelegate { @@ -48,10 +43,19 @@ extension CurrentLocationManager: LocationFetcherDelegate { self.currentLocationCompletion?(location) self.currentLocationCompletion = nil } + + func locationFetcher(_ fetcher: LocationFetcher, didChangeAuthorization authorization: CLAuthorizationStatus) { + self.authrizationCompletion?(authorization) + self.authrizationCompletion = nil + } } extension CurrentLocationManager: CLLocationManagerDelegate { func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { self.locationFetcher(manager, didUpdateLocations: locations) } + + func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { + self.locationFetcher(manager, didChangeAuthorization: manager.authorizationStatus) + } } diff --git a/iOS/Layover/Layover/Services/Location/LocationFetcher.swift b/iOS/Layover/Layover/Services/Location/LocationFetcher.swift index 057a71c..a76535e 100644 --- a/iOS/Layover/Layover/Services/Location/LocationFetcher.swift +++ b/iOS/Layover/Layover/Services/Location/LocationFetcher.swift @@ -10,10 +10,7 @@ import CoreLocation protocol LocationFetcherDelegate: AnyObject { func locationFetcher(_ fetcher: LocationFetcher, didUpdateLocations locations: [CLLocation]) - func locationFetcher( - _ fetcher: LocationFetcher, - didChangeAuthorization authorization: CLAuthorizationStatus - ) + func locationFetcher(_ fetcher: LocationFetcher, didChangeAuthorization authorization: CLAuthorizationStatus) } protocol LocationFetcher { diff --git a/iOS/Layover/LayoverTests/Mocks/LocationFetcher/MockLocationFetcher.swift b/iOS/Layover/LayoverTests/Mocks/LocationFetcher/MockLocationFetcher.swift index c505d3c..5287b2d 100644 --- a/iOS/Layover/LayoverTests/Mocks/LocationFetcher/MockLocationFetcher.swift +++ b/iOS/Layover/LayoverTests/Mocks/LocationFetcher/MockLocationFetcher.swift @@ -11,13 +11,15 @@ import CoreLocation @testable import Layover final class MockLocationFetcher: LocationFetcher { - var location: CLLocation? = CLLocation(latitude: 37.7749, longitude: 122.4194) + var location: CLLocation? var locationFetcherDelegate: Layover.LocationFetcherDelegate? var desiredAccuracy: CLLocationAccuracy = kCLLocationAccuracyBest + var locationCallBackValue: CLLocation? func requestLocation() { } func startUpdatingLocation() { + location = CLLocation(latitude: 37.498206, longitude: 127.02761) guard let location else { return } locationFetcherDelegate?.locationFetcher(self, didUpdateLocations: [location]) } From 956ed9300689d2b5992ea9727fd86253e20a6f1d Mon Sep 17 00:00:00 2001 From: kong <1018dbrud@gmail.com> Date: Wed, 13 Dec 2023 15:07:21 +0900 Subject: [PATCH 6/8] =?UTF-8?q?=F0=9F=94=A5=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=20=EC=BD=9C=EB=B0=B1=20value=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LayoverTests/Mocks/LocationFetcher/MockLocationFetcher.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/iOS/Layover/LayoverTests/Mocks/LocationFetcher/MockLocationFetcher.swift b/iOS/Layover/LayoverTests/Mocks/LocationFetcher/MockLocationFetcher.swift index 5287b2d..98481c0 100644 --- a/iOS/Layover/LayoverTests/Mocks/LocationFetcher/MockLocationFetcher.swift +++ b/iOS/Layover/LayoverTests/Mocks/LocationFetcher/MockLocationFetcher.swift @@ -14,7 +14,6 @@ final class MockLocationFetcher: LocationFetcher { var location: CLLocation? var locationFetcherDelegate: Layover.LocationFetcherDelegate? var desiredAccuracy: CLLocationAccuracy = kCLLocationAccuracyBest - var locationCallBackValue: CLLocation? func requestLocation() { } From 32a555551b9d98e7a860de6e98f35461dba72892 Mon Sep 17 00:00:00 2001 From: kong <1018dbrud@gmail.com> Date: Wed, 13 Dec 2023 21:32:04 +0900 Subject: [PATCH 7/8] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81,=20interactor=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=20async=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UploadPost/UploadPostInteractor.swift | 63 +++++++++---------- .../UploadPost/UploadPostViewController.swift | 10 ++- .../Location/CurrentLocationManager.swift | 6 +- .../Mocks/Workers/MockUploadPostWorker.swift | 3 +- .../UploadPostInteractorTests.swift | 13 ++-- .../UploadPost/UploadPostWorkerTests.swift | 4 +- 6 files changed, 48 insertions(+), 51 deletions(-) diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift index fd5073c..0a4a956 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostInteractor.swift @@ -15,8 +15,8 @@ import OSLog protocol UploadPostBusinessLogic { func fetchTags() func editTags(with request: UploadPostModels.EditTags.Request) - func fetchThumbnailImage() - func fetchCurrentAddress() + func fetchThumbnailImage() async + func fetchCurrentAddress() async func canUploadPost(request: UploadPostModels.CanUploadPost.Request) func uploadPost(request: UploadPostModels.UploadPost.Request) } @@ -64,43 +64,38 @@ final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostD tags = request.tags } - func fetchThumbnailImage() { + func fetchThumbnailImage() async { guard let videoURL else { return } let asset = AVAsset(url: videoURL) let generator = AVAssetImageGenerator(asset: asset) generator.appliesPreferredTrackTransform = true - Task { - do { - let image = try await generator.image(at: .zero).image - await MainActor.run { - presenter?.presentThumbnailImage(with: Models.FetchThumbnail.Response(thumbnailImage: image)) - } - } catch let error { - os_log(.error, log: .data, "Failed to fetch Thumbnail Image with error: %@", error.localizedDescription) + do { + let image = try await generator.image(at: .zero).image + await MainActor.run { + presenter?.presentThumbnailImage(with: Models.FetchThumbnail.Response(thumbnailImage: image)) } + } catch let error { + os_log(.error, log: .data, "Failed to fetch Thumbnail Image with error: %@", error.localizedDescription) } } - func fetchCurrentAddress() { + func fetchCurrentAddress() async { guard let location = locationManager.getCurrentLocation() else { return } let localeIdentifier = Locale.preferredLanguages.first != nil ? Locale.preferredLanguages[0] : Locale.current.identifier let locale = Locale(identifier: localeIdentifier) - - Task { - do { - let address = try await CLGeocoder().reverseGeocodeLocation(location, preferredLocale: locale).last - let administrativeArea = address?.administrativeArea - let locality = address?.locality - let subLocality = address?.subLocality - let response = Models.FetchCurrentAddress.Response(administrativeArea: administrativeArea, - locality: locality, - subLocality: subLocality) - await MainActor.run { - presenter?.presentCurrentAddress(with: response) - } - } catch { - os_log(.error, log: .data, "Failed to fetch Current Address with error: %@", error.localizedDescription) + do { + let address = try await CLGeocoder().reverseGeocodeLocation(location, preferredLocale: locale).last + let administrativeArea = address?.administrativeArea + let locality = address?.locality + let subLocality = address?.subLocality + let response = Models.FetchCurrentAddress.Response(administrativeArea: administrativeArea, + locality: locality, + subLocality: subLocality) + await MainActor.run { + presenter?.presentCurrentAddress(with: response) } + } catch { + os_log(.error, log: .data, "Failed to fetch Current Address with error: %@", error.localizedDescription) } } @@ -119,11 +114,11 @@ final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostD } Task { let uploadPostResponse = await worker.uploadPost(with: UploadPost(title: request.title, - content: request.content, - latitude: coordinate.latitude, - longitude: coordinate.longitude, - tag: request.tags, - videoURL: videoURL)) + content: request.content, + latitude: coordinate.latitude, + longitude: coordinate.longitude, + tag: request.tags, + videoURL: videoURL)) guard let boardID = uploadPostResponse?.id else { return } let fileType = videoURL.pathExtension _ = await worker.uploadVideo(with: UploadVideoRequestDTO(boardID: boardID, filetype: fileType), @@ -144,8 +139,8 @@ final class UploadPostInteractor: NSObject, UploadPostBusinessLogic, UploadPostD let timeRange: CMTimeRange = CMTimeRangeMake(start: .zero, duration: sourceAssetduration) try compositionVideoTrack.insertTimeRange(timeRange, - of: sourceVideoTrack, - at: .zero) + of: sourceVideoTrack, + at: .zero) if fileManager.fileExists(atPath: url.path()) { try fileManager.removeItem(at: url) diff --git a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift index 264fe48..917387e 100644 --- a/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift +++ b/iOS/Layover/Layover/Scenes/UploadPost/UploadPostViewController.swift @@ -141,8 +141,7 @@ final class UploadPostViewController: BaseViewController { setConstraints() setDelegation() addTarget() - interactor?.fetchThumbnailImage() - interactor?.fetchCurrentAddress() + fetchPostInfo() } override func viewWillAppear(_ animated: Bool) { @@ -174,6 +173,13 @@ final class UploadPostViewController: BaseViewController { setContentViewSubviewsConstraints() } + private func fetchPostInfo() { + Task { + await interactor?.fetchCurrentAddress() + await interactor?.fetchThumbnailImage() + } + } + private func setDelegation() { titleTextField.delegate = self contentTextView.delegate = self diff --git a/iOS/Layover/Layover/Services/Location/CurrentLocationManager.swift b/iOS/Layover/Layover/Services/Location/CurrentLocationManager.swift index 38f8c38..7649be4 100644 --- a/iOS/Layover/Layover/Services/Location/CurrentLocationManager.swift +++ b/iOS/Layover/Layover/Services/Location/CurrentLocationManager.swift @@ -8,7 +8,7 @@ import CoreLocation -class CurrentLocationManager: NSObject { +final class CurrentLocationManager: NSObject { typealias LocationCompletion = ((CLLocation) -> Void) @@ -25,10 +25,8 @@ class CurrentLocationManager: NSObject { func getCurrentLocation() -> CLLocation? { startUpdatingLocation() - guard let space = locationFetcher.location?.coordinate else { return nil } - let location = CLLocation(latitude: space.latitude, longitude: space.longitude) - return location + return CLLocation(latitude: space.latitude, longitude: space.longitude) } func startUpdatingLocation() { diff --git a/iOS/Layover/LayoverTests/Mocks/Workers/MockUploadPostWorker.swift b/iOS/Layover/LayoverTests/Mocks/Workers/MockUploadPostWorker.swift index 857fbd6..d74df6c 100644 --- a/iOS/Layover/LayoverTests/Mocks/Workers/MockUploadPostWorker.swift +++ b/iOS/Layover/LayoverTests/Mocks/Workers/MockUploadPostWorker.swift @@ -15,7 +15,8 @@ final class MockUploadPostWorker: UploadPostWorkerProtocol { private let provider: ProviderType - init(provider: ProviderType) { + init(provider: ProviderType = Provider(session: .initMockSession(), + authManager: StubAuthManager())) { self.provider = provider } diff --git a/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostInteractorTests.swift b/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostInteractorTests.swift index 26194ae..d508455 100644 --- a/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostInteractorTests.swift +++ b/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostInteractorTests.swift @@ -97,14 +97,13 @@ class UploadPostInteractorTests: XCTestCase { XCTAssertNotNil(sut.tags, "fetchTags 함수가 datastore에 tags 데이터를 전달하지 못함") } - func test_fetchCurrentAddress를_호출하면_presenter의_presentCurrentAddress가_호출된다() async throws { + func test_fetchCurrentAddress를_호출하면_presenter의_presentCurrentAddress가_호출된다() async { // given let spy = UploadPostPresentationLogicSpy() sut.presenter = spy // when - sut.fetchCurrentAddress() - try await Task.sleep(nanoseconds: 3_000_000_000) + await sut.fetchCurrentAddress() // then XCTAssertTrue(spy.presentCurrentAddressCalled, "fetchCurrentAddress 함수가 presentCurrentAddress을 호출하지 못함") @@ -116,22 +115,20 @@ class UploadPostInteractorTests: XCTestCase { sut.presenter = spy // when - sut.fetchCurrentAddress() - try await Task.sleep(nanoseconds: 3_000_000_000) + await sut.fetchCurrentAddress() // then XCTAssertNotNil(spy.presentCurrentAddressResponse, "fetchCurrentAddress 함수가 presenter에게 위치데이터를 전달하지 못함") } - func test_fetchThumbnailImage를_호출하면_presenter의_presentThumbnailImage가_호출된다() async throws { + func test_fetchThumbnailImage를_호출하면_presenter의_presentThumbnailImage가_호출된다() async { // given let spy = UploadPostPresentationLogicSpy() sut.presenter = spy sut.videoURL = URL(string: "https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8") // when - sut.fetchThumbnailImage() - try await Task.sleep(nanoseconds: 5_000_000_000) + await sut.fetchThumbnailImage() // then XCTAssertTrue(spy.presentThumbnailCalled, "fetchThumbnailImage 함수가 presentThumbnailImage을 호출하지 못함") diff --git a/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostWorkerTests.swift b/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostWorkerTests.swift index f7f6140..3ca5e52 100644 --- a/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostWorkerTests.swift +++ b/iOS/Layover/LayoverTests/Scenes/UploadPost/UploadPostWorkerTests.swift @@ -34,7 +34,7 @@ class UploadPostWorkerTests: XCTestCase { sut = UploadPostWorker(provider: Provider(session: .initMockSession(), authManager: StubAuthManager())) } - func test_uploadPost는_업로드에_성공하면_올바른결과를_반환한다() async throws { + func test_uploadPost는_업로드에_성공하면_올바른결과를_반환한다() async { // given guard let mockFileLocation = Bundle(for: type(of: self)).url(forResource: "PostBoard", withExtension: "json"), let mockData = try? Data(contentsOf: mockFileLocation) else { @@ -59,7 +59,7 @@ class UploadPostWorkerTests: XCTestCase { // when let response = await sut.uploadPost(with: request) - try await Task.sleep(nanoseconds: 3_000_000_000) +// try await Task.sleep(nanoseconds: 3_000_000_000) // then XCTAssertNotNil(response, "uploadPost가 response를 정상적으로 반환하지 못함") From b6c15417a29c9341c10f6f726e019dc6961f038f Mon Sep 17 00:00:00 2001 From: kong <1018dbrud@gmail.com> Date: Wed, 13 Dec 2023 22:22:08 +0900 Subject: [PATCH 8/8] =?UTF-8?q?=F0=9F=90=9B=20try!=EB=A1=9C=20=EC=9D=B8?= =?UTF-8?q?=ED=95=9C=20=EB=A6=B0=ED=8A=B8=20=EC=97=90=EB=9F=AC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Layover/LayoverTests/Seeds.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/Layover/LayoverTests/Seeds.swift b/iOS/Layover/LayoverTests/Seeds.swift index f64fb9a..294d99c 100644 --- a/iOS/Layover/LayoverTests/Seeds.swift +++ b/iOS/Layover/LayoverTests/Seeds.swift @@ -10,7 +10,7 @@ import Foundation // JSON 데이터와 비교하기 위한 모델 class Seeds { - static let sampleImageData = try! Data(contentsOf: Bundle(for: Seeds.self).url(forResource: "sample", withExtension: "jpeg")!) + static let sampleImageData = try? Data(contentsOf: Bundle(for: Seeds.self).url(forResource: "sample", withExtension: "jpeg")!) struct Posts { // PostList.json에 정의된 데이터