diff --git a/BBus/BBus.xcodeproj/project.pbxproj b/BBus/BBus.xcodeproj/project.pbxproj index a890e5a2..c7f1d888 100644 --- a/BBus/BBus.xcodeproj/project.pbxproj +++ b/BBus/BBus.xcodeproj/project.pbxproj @@ -151,6 +151,11 @@ 4AF1E0C32756269F00DE51C8 /* BusRouteViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AF1E0C22756269F00DE51C8 /* BusRouteViewModelTests.swift */; }; 4AF1E0D0275626A700DE51C8 /* AlarmSettingViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AF1E0CF275626A700DE51C8 /* AlarmSettingViewModelTests.swift */; }; 4AF1E0DD275626B300DE51C8 /* MovingStatusViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AF1E0DC275626B300DE51C8 /* MovingStatusViewModelTests.swift */; }; + 4AF1E0E327566C8600DE51C8 /* PersistenceStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87038A8B273B96DF0078EAE3 /* PersistenceStorage.swift */; }; + 4AF1E0E42756725900DE51C8 /* PublisherExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A38E20827464015003A9D10 /* PublisherExtension.swift */; }; + 4AF1E0E62756734A00DE51C8 /* BBusAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A38E20627463FF7003A9D10 /* BBusAPIError.swift */; }; + 4AF1E0E72756735B00DE51C8 /* FavoriteItemDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A06AEC7274159D10027222D /* FavoriteItemDTO.swift */; }; + 4AF1E0E827567DE100DE51C8 /* JsonDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0446843C275679B9007E440A /* JsonDTO.swift */; }; 87038A82273B90A50078EAE3 /* BBusAPIUseCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87038A81273B90A50078EAE3 /* BBusAPIUseCases.swift */; }; 87038A88273B950B0078EAE3 /* GetArrInfoByRouteListFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87038A87273B950B0078EAE3 /* GetArrInfoByRouteListFetcher.swift */; }; 87038A8A273B96B60078EAE3 /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87038A89273B96B60078EAE3 /* NetworkService.swift */; }; @@ -1694,6 +1699,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4AF1E0E827567DE100DE51C8 /* JsonDTO.swift in Sources */, + 4AF1E0E72756735B00DE51C8 /* FavoriteItemDTO.swift in Sources */, + 4AF1E0E62756734A00DE51C8 /* BBusAPIError.swift in Sources */, + 4AF1E0E42756725900DE51C8 /* PublisherExtension.swift in Sources */, + 4AF1E0E327566C8600DE51C8 /* PersistenceStorage.swift in Sources */, 4AF1E0752756263400DE51C8 /* PersistenceStorageTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2107,7 +2117,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = B3PWYBKFUK; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2131,7 +2141,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = B3PWYBKFUK; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.4; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/BBus/PersistenceStorageTests/PersistenceStorageTests.swift b/BBus/PersistenceStorageTests/PersistenceStorageTests.swift index c17e893b..9ac40c03 100644 --- a/BBus/PersistenceStorageTests/PersistenceStorageTests.swift +++ b/BBus/PersistenceStorageTests/PersistenceStorageTests.swift @@ -6,27 +6,202 @@ // import XCTest +import Combine class PersistenceStorageTests: XCTestCase { + private let key: String = "PersistenceStorageTestKey" + private let key2: String = "PersistenceStorageTestKey2" + private let key3: String = "PersistenceStorageTestKey3" + private let key4: String = "PersistenceStorageTestKey4" + private let fileName: String = "BusRouteList" + private let fileType: String = "json" + private var cancellables: Set = [] + override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. + self.cancellables = [] } - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. + struct DummyCodable: Codable, Equatable { + let dummy: String } - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. + func test_create_성공() { + + //given + UserDefaults.standard.removeObject(forKey: self.key) + let storage = PersistenceStorage() + let param = DummyCodable(dummy: "dummy") + let expectation = self.expectation(description: "create가 에러 없이 정상 작동해야함") + var resultOpt: Data? + + //when + storage.create(key: self.key, param: param) + .catchError { error in + XCTFail() + } + .sink { data in + resultOpt = data + expectation.fulfill() + } + .store(in: &self.cancellables) + + //then + waitForExpectations(timeout: 2) + guard let result = resultOpt else { XCTFail(); return } + let decodedResultOpt = try? PropertyListDecoder().decode(DummyCodable.self, from: result) + guard let decodedResult = decodedResultOpt else { XCTFail(); return } + XCTAssertEqual(decodedResult, param, "생성하려는 객체가 정상적으로 리턴되지 않았습니다.") } - func testPerformanceExample() throws { - // This is an example of a performance test case. - measure { - // Put the code you want to measure the time of here. - } + func test_getFromUserDefault_성공() { + + //given + UserDefaults.standard.removeObject(forKey: self.key2) + + let storage = PersistenceStorage() + let param = DummyCodable(dummy: "dummy") + let createExpect = self.expectation(description: "목 데이터 생성이 선행되어야 함") + let expectation = self.expectation(description: "getFromUserDefault가 에러 없이 정상 작동해야함") + var resultOpt: Data? + + storage.create(key: self.key2, param: param) + .catchError { error in + XCTFail() + } + .sink { _ in + createExpect.fulfill() + } + .store(in: &self.cancellables) + + wait(for: [createExpect], timeout: 5) + //when + storage.getFromUserDefaults(key: self.key2) + .catchError { error in + XCTFail() + } + .sink { data in + resultOpt = data + expectation.fulfill() + } + .store(in: &self.cancellables) + + //then + wait(for: [expectation], timeout: 5) + guard let result = resultOpt else { XCTFail(); return } + let decodedResultOpt = try? PropertyListDecoder().decode([DummyCodable].self, from: result) + guard let decodedResult = decodedResultOpt else { XCTFail(); return } + XCTAssertEqual(decodedResult, [param], "저장된 객체를 불러오지 못했습니다.") + } + + func test_getFromUserDefault_실패() { + + //given + UserDefaults.standard.removeObject(forKey: self.key3) + let storage = PersistenceStorage() + let expectation = self.expectation(description: "데이터가 없기 때문에, getFromUserDefault가 작동하지않음") + var resultOpt: Data? + + //when + storage.getFromUserDefaults(key: self.key3) + .catchError { error in + XCTFail() + } + .sink { data in + resultOpt = data + expectation.fulfill() + } + .store(in: &self.cancellables) + + let emptyArrayDataOpt = try? PropertyListEncoder().encode([FavoriteItemDTO]()) + + //then + waitForExpectations(timeout: 5) + guard let result = resultOpt, + let emptyArrayData = emptyArrayDataOpt else { XCTFail(); return } + XCTAssertEqual(result, emptyArrayData, "저장된 데이터가 존재하면 안됩니다.") } + func test_실제저장소_get_BusRouteList_수신성공() { + + //given + let storage = PersistenceStorage() + let expectation = self.expectation(description: "PersistenceStorageRealGet") + + //when + storage.get(file: self.fileName, type: self.fileType) + .catchError { error in + XCTFail() + } + .sink { _ in + expectation.fulfill() + } + .store(in: &self.cancellables) + + //then + waitForExpectations(timeout: 2) + } + + func test_실제저장소_get_BusRouteList_수신실패() { + + //given + let storage = PersistenceStorage() + let expectation = self.expectation(description: "PersistenceStorageRealGet2") + + //when + storage.get(file: "아무파일이름입니다.", type: self.fileType) + .catchError { error in + expectation.fulfill() + } + .sink { data in + XCTFail() + } + .store(in: &self.cancellables) + + //then + waitForExpectations(timeout: 2) + } + + + func test_실제저장소_delete_성공() { + //given + UserDefaults.standard.removeObject(forKey: self.key4) + let storage = PersistenceStorage() + let expectation = self.expectation(description: "데이터를 delete 성공해야함") + var resultOpt: Data? + var params = [DummyCodable(dummy: "dummy1"), DummyCodable(dummy: "dummy2"), DummyCodable(dummy: "dummy3"), DummyCodable(dummy: "dummy4")] + + for param in params { + let createExpect = self.expectation(description: "delete를 위한 목 데이터 생성이 완료되어야함") + storage.create(key: self.key4, param: param) + .catchError { error in + XCTFail() + } + .sink { _ in + // 동시접근 이슈 + createExpect.fulfill() + } + .store(in: &self.cancellables) + wait(for: [createExpect], timeout: 5) + } + + //when + storage.delete(key: self.key4, param: params[2]) + .catchError { error in + XCTFail() + } + .sink { data in + resultOpt = data + expectation.fulfill() + } + .store(in: &self.cancellables) + params.remove(at: 2) // 배열에서도 제거, 비교하기 위함임 + + //then + wait(for: [expectation], timeout: 5) + guard let result = resultOpt else { XCTFail(); return } + let decodedResultOpt = try? PropertyListDecoder().decode([DummyCodable].self, from: result) + guard let decodedResult = decodedResultOpt else { XCTFail(); return } + XCTAssertEqual(params, decodedResult, "제거된 데이터와 상이합니다.") + } }