diff --git a/BBus/BBus.xcodeproj/project.pbxproj b/BBus/BBus.xcodeproj/project.pbxproj index e6dad3bb..c9335ba7 100644 --- a/BBus/BBus.xcodeproj/project.pbxproj +++ b/BBus/BBus.xcodeproj/project.pbxproj @@ -160,6 +160,31 @@ 87038A92273C12320078EAE3 /* GetBusPosByRtidFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87038A91273C12320078EAE3 /* GetBusPosByRtidFetcher.swift */; }; 87038A94273C12E20078EAE3 /* GetStationByUidItemFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87038A93273C12E20078EAE3 /* GetStationByUidItemFetcher.swift */; }; 87115EFA27564D0F00601770 /* RequestFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A5182A527550988001EA530 /* RequestFactory.swift */; }; + 87115EFB27565A3300601770 /* BusRouteViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ACA520A272FCE5F00EC0531 /* BusRouteViewModel.swift */; }; + 87115EFC27565BAD00601770 /* BusRouteAPIUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ACA5208272FCE5A00EC0531 /* BusRouteAPIUseCase.swift */; }; + 87115EFD27565D9D00601770 /* BusRouteDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 049375AA273B9F330061ACDA /* BusRouteDTO.swift */; }; + 87115EFE27565DAE00601770 /* StationByRouteListDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 049375B4273BB98E0061ACDA /* StationByRouteListDTO.swift */; }; + 87115EFF27565DC200601770 /* BusPosByRtidDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 049375B6273BE08F0061ACDA /* BusPosByRtidDTO.swift */; }; + 87115F00275667A600601770 /* BaseUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A5100322754CDB100754B36 /* BaseUseCase.swift */; }; + 87115F01275668E100601770 /* BusCongestion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ACA5212272FCE8500EC0531 /* BusCongestion.swift */; }; + 87115F02275668F600601770 /* NotificationNameExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A06AEE82743DAB20027222D /* NotificationNameExtension.swift */; }; + 87115F032756693700601770 /* BusPosByVehicleIdDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ADB29BA274B6C8300554A4E /* BusPosByVehicleIdDTO.swift */; }; + 87115F042756694600601770 /* PublisherExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A38E20827464015003A9D10 /* PublisherExtension.swift */; }; + 87115F052756696F00601770 /* GetRouteListUsable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A5182B627551ED8001EA530 /* GetRouteListUsable.swift */; }; + 87115F062756698D00601770 /* BBusAPIUseCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87038A81273B90A50078EAE3 /* BBusAPIUseCases.swift */; }; + 87115F0727566A4400601770 /* GetStationsByRouteListUsable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A5182B027551E84001EA530 /* GetStationsByRouteListUsable.swift */; }; + 87115F0827566A5200601770 /* TokenManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A5182A32754E3CA001EA530 /* TokenManager.swift */; }; + 87115F0927566A5900601770 /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87038A89273B96B60078EAE3 /* NetworkService.swift */; }; + 87115F0A27566A6200601770 /* PersistenceStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87038A8B273B96DF0078EAE3 /* PersistenceStorage.swift */; }; + 87115F0B27566A6900601770 /* RequestFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A5182A527550988001EA530 /* RequestFactory.swift */; }; + 87115F0C27566A7000601770 /* GetStationsByRouteListFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87038A8F273C11630078EAE3 /* GetStationsByRouteListFetcher.swift */; }; + 87115F0D27566A7800601770 /* ServiceFetchable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A5182A7275511D5001EA530 /* ServiceFetchable.swift */; }; + 87115F0E27566A8000601770 /* GetBusPosByRtidUsable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A5182B227551EA9001EA530 /* GetBusPosByRtidUsable.swift */; }; + 87115F0F27566A8E00601770 /* GetBusPosByRtidFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87038A91273C12320078EAE3 /* GetBusPosByRtidFetcher.swift */; }; + 87115F1027566A9800601770 /* GetRouteListFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AA294AE273C0E8D008E5497 /* GetRouteListFetcher.swift */; }; + 87115F1127566A9F00601770 /* BBusAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A38E20627463FF7003A9D10 /* BBusAPIError.swift */; }; + 87115F1227566AA700601770 /* PersistencetFetchable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A5182AB275516A4001EA530 /* PersistencetFetchable.swift */; }; + 87115F1327566AB200601770 /* FavoriteItemDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A06AEC7274159D10027222D /* FavoriteItemDTO.swift */; }; 87115F172756758800601770 /* GetArrInfoByRouteListUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87115F162756758800601770 /* GetArrInfoByRouteListUseCase.swift */; }; 87115F19275675B900601770 /* GetStationsByRouteListUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87115F18275675B900601770 /* GetStationsByRouteListUseCase.swift */; }; 87115F1B275675E200601770 /* GetBusPosByRtidUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87115F1A275675E200601770 /* GetBusPosByRtidUseCase.swift */; }; @@ -177,6 +202,8 @@ 873D639827303A6800E79069 /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873D639727303A6800E79069 /* AppCoordinator.swift */; }; 873D639A27303B0500E79069 /* HomeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873D639927303B0500E79069 /* HomeCoordinator.swift */; }; 873D639C27303B5000E79069 /* SearchCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873D639B27303B5000E79069 /* SearchCoordinator.swift */; }; + 875483782756810400136F16 /* BusRouteAPIUsable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0446842E275676B3007E440A /* BusRouteAPIUsable.swift */; }; + 875483792756810D00136F16 /* JsonDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0446843C275679B9007E440A /* JsonDTO.swift */; }; 875F1AB02755BD86003F5BB1 /* AverageSectionTimeCalculatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 875F1AAF2755BD86003F5BB1 /* AverageSectionTimeCalculatable.swift */; }; 875F1AB22755BE08003F5BB1 /* AlarmSettingCalculateUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 875F1AB12755BE08003F5BB1 /* AlarmSettingCalculateUseCase.swift */; }; 87A5556C2728116400A9B5E3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87A5556B2728116400A9B5E3 /* AppDelegate.swift */; }; @@ -1744,7 +1771,34 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 87115F0F27566A8E00601770 /* GetBusPosByRtidFetcher.swift in Sources */, + 875483792756810D00136F16 /* JsonDTO.swift in Sources */, + 87115F1027566A9800601770 /* GetRouteListFetcher.swift in Sources */, + 87115F0B27566A6900601770 /* RequestFactory.swift in Sources */, + 87115F042756694600601770 /* PublisherExtension.swift in Sources */, + 87115F02275668F600601770 /* NotificationNameExtension.swift in Sources */, + 87115F052756696F00601770 /* GetRouteListUsable.swift in Sources */, + 87115F1327566AB200601770 /* FavoriteItemDTO.swift in Sources */, + 87115F00275667A600601770 /* BaseUseCase.swift in Sources */, + 87115F01275668E100601770 /* BusCongestion.swift in Sources */, + 87115F062756698D00601770 /* BBusAPIUseCases.swift in Sources */, + 87115EFF27565DC200601770 /* BusPosByRtidDTO.swift in Sources */, + 87115F0D27566A7800601770 /* ServiceFetchable.swift in Sources */, + 87115F0C27566A7000601770 /* GetStationsByRouteListFetcher.swift in Sources */, + 87115F0827566A5200601770 /* TokenManager.swift in Sources */, + 87115F1227566AA700601770 /* PersistencetFetchable.swift in Sources */, + 87115F032756693700601770 /* BusPosByVehicleIdDTO.swift in Sources */, + 87115EFE27565DAE00601770 /* StationByRouteListDTO.swift in Sources */, + 875483782756810400136F16 /* BusRouteAPIUsable.swift in Sources */, + 87115F0927566A5900601770 /* NetworkService.swift in Sources */, + 87115EFD27565D9D00601770 /* BusRouteDTO.swift in Sources */, + 87115F0727566A4400601770 /* GetStationsByRouteListUsable.swift in Sources */, + 87115EFC27565BAD00601770 /* BusRouteAPIUseCase.swift in Sources */, + 87115EFB27565A3300601770 /* BusRouteViewModel.swift in Sources */, + 87115F0A27566A6200601770 /* PersistenceStorage.swift in Sources */, + 87115F1127566A9F00601770 /* BBusAPIError.swift in Sources */, 4AF1E0C32756269F00DE51C8 /* BusRouteViewModelTests.swift in Sources */, + 87115F0E27566A8000601770 /* GetBusPosByRtidUsable.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/BBus/BBus/Global/DTO/BusPosByRtidDTO.swift b/BBus/BBus/Global/DTO/BusPosByRtidDTO.swift index 02f498ee..b19c81ff 100644 --- a/BBus/BBus/Global/DTO/BusPosByRtidDTO.swift +++ b/BBus/BBus/Global/DTO/BusPosByRtidDTO.swift @@ -49,4 +49,15 @@ struct BusPosByRtidDTO: Codable { self.gpsY = Double((try? container.decode(String.self, forKey: .gpsY)) ?? "") ?? 0 self.gpsX = Double((try? container.decode(String.self, forKey: .gpsX)) ?? "") ?? 0 } + + init(busType: Int, congestion: Int, plainNumber: String, sectionOrder: Int, fullSectDist: String, sectDist: String, gpsY: Double, gpsX: Double) { + self.busType = busType + self.congestion = congestion + self.plainNumber = plainNumber + self.sectionOrder = sectionOrder + self.fullSectDist = fullSectDist + self.sectDist = sectDist + self.gpsY = gpsY + self.gpsX = gpsX + } } diff --git a/BBus/BBus/Global/DTO/StationByRouteListDTO.swift b/BBus/BBus/Global/DTO/StationByRouteListDTO.swift index 3b45bfe5..09b30194 100644 --- a/BBus/BBus/Global/DTO/StationByRouteListDTO.swift +++ b/BBus/BBus/Global/DTO/StationByRouteListDTO.swift @@ -49,4 +49,15 @@ struct StationByRouteListDTO: Codable { self.lastTm = (try? container.decode(String.self, forKey: .lastTm)) ?? "" self.transYn = (try? container.decode(String.self, forKey: .transYn)) ?? "" } + + init(sectionSpeed: Int, sequence: Int, stationName: String, fullSectionDistance: Int, arsId: String, beginTm: String, lastTm: String, transYn: String) { + self.sectionSpeed = sectionSpeed + self.sequence = sequence + self.stationName = stationName + self.fullSectionDistance = fullSectionDistance + self.arsId = arsId + self.beginTm = beginTm + self.lastTm = lastTm + self.transYn = transYn + } } diff --git a/BBus/BusRouteViewModelTests/BusRouteViewModelTests.swift b/BBus/BusRouteViewModelTests/BusRouteViewModelTests.swift index dde97386..70118667 100644 --- a/BBus/BusRouteViewModelTests/BusRouteViewModelTests.swift +++ b/BBus/BusRouteViewModelTests/BusRouteViewModelTests.swift @@ -2,31 +2,220 @@ // BusRouteViewModelTests.swift // BusRouteViewModelTests // -// Created by 김태훈 on 2021/11/30. +// Created by Kang Minsang on 2021/12/01. // import XCTest +import Foundation +import Combine class BusRouteViewModelTests: XCTestCase { + + var busRouteViewModel: BusRouteViewModel? + var cancellables: Set = [] + + class DummyBusRouteAPIUseCase: BusRouteAPIUsable { + func searchHeader(busRouteId: Int) -> AnyPublisher { + let dummyDTO = BusRouteDTO(routeID: 100100260, + busRouteName: "5524", + routeType: .localLine, + startStation: "난향차고지", + endStation: "중앙대학교") + return Just(dummyDTO).setFailureType(to: Error.self).eraseToAnyPublisher() + } + + func fetchRouteList(busRouteId: Int) -> AnyPublisher<[StationByRouteListDTO], Error> { + let dummyStation1 = StationByRouteListDTO(sectionSpeed: 0, + sequence: 1, + stationName: "난곡종점", + fullSectionDistance: 0, + arsId: "21809", + beginTm: "04:00", + lastTm: "22:30", + transYn: "N") + let dummyStation2 = StationByRouteListDTO(sectionSpeed: 44, + sequence: 2, + stationName: "신림복지관앞", + fullSectionDistance: 247, + arsId: "21211", + beginTm: "04:00", + lastTm: "00:18", + transYn: "N") + let dummyStation3 = StationByRouteListDTO(sectionSpeed: 29, + sequence: 3, + stationName: "난우중학교입구", + fullSectionDistance: 190, + arsId: "21210", + beginTm: "04:00", + lastTm: "22:30", + transYn: "N") + return Just([dummyStation1, dummyStation2, dummyStation3]).setFailureType(to: Error.self).eraseToAnyPublisher() + } + + func fetchBusPosList(busRouteId: Int) -> AnyPublisher<[BusPosByRtidDTO], Error> { + let dummyBus1 = BusPosByRtidDTO(busType: 1, + congestion: 0, + plainNumber: "서울74사5255", + sectionOrder: 22, + fullSectDist: "0.351", + sectDist: "0", + gpsY: 37.4893, + gpsX: 126.927062) + let dummyBus2 = BusPosByRtidDTO(busType: 1, + congestion: 0, + plainNumber: "서울74사5254", + sectionOrder: 28, + fullSectDist: "0.378", + sectDist: "0.017", + gpsY: 37.486795, + gpsX: 126.947757) + let dummyBus3 = BusPosByRtidDTO(busType: 1, + congestion: 0, + plainNumber: "서울74사5252", + sectionOrder: 32, + fullSectDist: "0.41", + sectDist: "0.022", + gpsY: 37.48311, + gpsX: 126.954122) + return Just([dummyBus1, dummyBus2, dummyBus3]).setFailureType(to: Error.self).eraseToAnyPublisher() + } + } override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. + super.setUp() + self.busRouteViewModel = BusRouteViewModel(useCase: DummyBusRouteAPIUseCase(), busRouteId: 100100260) } override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + self.busRouteViewModel = nil } - 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_bindHeaderInfo_수신_성공() throws { + // given + guard let viewModel = self.busRouteViewModel else { + XCTFail("viewModel is nil") + return + } + let expectation = XCTestExpectation() + let answerDTO = BusRouteDTO(routeID: 100100260, + busRouteName: "5524", + routeType: .localLine, + startStation: "난향차고지", + endStation: "중앙대학교") + + // when + viewModel.$header + .receive(on: DispatchQueue.global()) + .sink { completion in + // then + guard case .failure(let error) = completion else { return } + XCTFail("\(error.localizedDescription)") + expectation.fulfill() + } receiveValue: { header in + guard let header = header else { return } + // then + XCTAssertEqual(header.routeID, answerDTO.routeID) + XCTAssertEqual(header.busRouteName, answerDTO.busRouteName) + XCTAssertEqual(header.routeType, answerDTO.routeType) + XCTAssertEqual(header.startStation, answerDTO.startStation) + XCTAssertEqual(header.endStation, answerDTO.endStation) + expectation.fulfill() + } + .store(in: &self.cancellables) + + wait(for: [expectation], timeout: 2) } - - 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_bindBodysInfo_수신_성공() throws { + // given + guard let viewModel = self.busRouteViewModel else { + XCTFail("viewModel is nil") + return } + let expectation = XCTestExpectation() + let station1 = BusStationInfo(speed: 0, afterSpeed: 44, count: 3, title: "난곡종점", description: "21809 | 04:00-22:30", transYn: "N", arsId: "21809") + let station2 = BusStationInfo(speed: 44, afterSpeed: 29, count: 3, title: "신림복지관앞", description: "21211 | 04:00-00:18", transYn: "N", arsId: "21211") + let station3 = BusStationInfo(speed: 29, afterSpeed: nil, count: 3, title: "난우중학교입구", description: "21210 | 04:00-22:30", transYn: "N", arsId: "21210") + let answerStations = [station1, station2, station3] + + // when + viewModel.$bodys + .receive(on: DispatchQueue.global()) + .sink { completion in + // then + guard case .failure(let error) = completion else { return } + XCTFail("\(error.localizedDescription)") + expectation.fulfill() + } receiveValue: { bodys in + // then + XCTAssertEqual(bodys[0].arsId, answerStations[0].arsId) + XCTAssertEqual(bodys[1].description, answerStations[1].description) + XCTAssertEqual(bodys[2].afterSpeed, answerStations[2].afterSpeed) + expectation.fulfill() + } + .store(in: &self.cancellables) + + wait(for: [expectation], timeout: 2) + } + + func test_bindBusesPosInfo_수신_성공() throws { + // given + guard let viewModel = self.busRouteViewModel else { + XCTFail("viewModel is nil") + return + } + let expectation = XCTestExpectation() + let bus1 = BusPosInfo(location: CGFloat(21), number: "5255", congestion: .normal, islower: true) + let bus2 = BusPosInfo(location: CGFloat(27) + CGFloat(("0.017" as NSString).floatValue)/CGFloat(("0.378" as NSString).floatValue), number: "5254", congestion: .normal, islower: true) + let bus3 = BusPosInfo(location: CGFloat(31) + CGFloat(0.022)/CGFloat(0.41), number: "5252", congestion: .normal, islower: true) + let answerBuses = [bus1, bus2, bus3] + + // when + viewModel.$buses + .receive(on: DispatchQueue.global()) + .dropFirst() + .sink { completion in + // then + guard case .failure(let error) = completion else { return } + XCTFail("\(error.localizedDescription)") + expectation.fulfill() + } receiveValue: { buses in + // then + XCTAssertEqual(buses[0].number, answerBuses[0].number) + XCTAssertEqual(buses[1].location, answerBuses[1].location) + XCTAssertEqual(buses[2].congestion, answerBuses[2].congestion) + expectation.fulfill() + } + .store(in: &self.cancellables) + + wait(for: [expectation], timeout: 2) + } + + func test_bindLoader() throws { + // given + guard let viewModel = self.busRouteViewModel else { + XCTFail("viewModel is nil") + return + } + let expectation = XCTestExpectation() + + // when + viewModel.$stopLoader + .receive(on: DispatchQueue.global()) + .dropFirst() + .sink { completion in + // then + guard case .failure(let error) = completion else { return } + XCTFail("\(error.localizedDescription)") + expectation.fulfill() + } receiveValue: { loader in + // then + XCTAssertTrue(loader) + expectation.fulfill() + } + .store(in: &self.cancellables) + + wait(for: [expectation], timeout: 2) } - }