From eef1a4d753b1c3acfae73aac8cbe652679c8c597 Mon Sep 17 00:00:00 2001 From: kwzr Date: Mon, 18 Mar 2024 10:29:53 +0900 Subject: [PATCH] Add initial test cases (#38) * Add initial test case * Add a negative test case * Back to Dependency Client style * Move mock to test * Update MyLibrary/Sources/ScheduleFeature/Schedule.swift Co-authored-by: Daiki Matsudate * Remove testValue = Self() * Create struct for response --------- Co-authored-by: Daiki Matsudate --- App/App.xcodeproj/project.pbxproj | 2 + .../xcshareddata/xcschemes/App.xcscheme | 9 +- App/App/App.xctestplan | 29 ++++++ MyLibrary/Package.swift | 7 ++ .../Sources/ScheduleFeature/Schedule.swift | 30 +++++- .../Tests/MyLibraryTests/MyLibraryTests.swift | 13 --- .../Tests/ScheduleFeatureTests/Mocks.swift | 97 +++++++++++++++++++ .../ScheduleFeatureTests/ScheduleTests.swift | 38 ++++++++ 8 files changed, 205 insertions(+), 20 deletions(-) create mode 100644 App/App/App.xctestplan delete mode 100644 MyLibrary/Tests/MyLibraryTests/MyLibraryTests.swift create mode 100644 MyLibrary/Tests/ScheduleFeatureTests/Mocks.swift create mode 100644 MyLibrary/Tests/ScheduleFeatureTests/ScheduleTests.swift diff --git a/App/App.xcodeproj/project.pbxproj b/App/App.xcodeproj/project.pbxproj index 5445e6e..37af345 100644 --- a/App/App.xcodeproj/project.pbxproj +++ b/App/App.xcodeproj/project.pbxproj @@ -44,6 +44,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 2435021A2BA49F29000083BB /* App.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = App.xctestplan; sourceTree = ""; }; D563615B2B931FF800E4F789 /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; }; D563615E2B931FF800E4F789 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; D56361642B931FF900E4F789 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -106,6 +107,7 @@ D56361662B931FF900E4F789 /* App.entitlements */, D563616A2B931FF900E4F789 /* Info.plist */, D583D7C12B9AD2ED00B94F73 /* PrivacyInfo.xcprivacy */, + 2435021A2BA49F29000083BB /* App.xctestplan */, D56361672B931FF900E4F789 /* Preview Content */, ); path = App; diff --git a/App/App.xcodeproj/xcshareddata/xcschemes/App.xcscheme b/App/App.xcodeproj/xcshareddata/xcschemes/App.xcscheme index 66fd886..d1c6e65 100644 --- a/App/App.xcodeproj/xcshareddata/xcschemes/App.xcscheme +++ b/App/App.xcodeproj/xcshareddata/xcschemes/App.xcscheme @@ -27,8 +27,13 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES" - shouldAutocreateTestPlan = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES"> + + + + ) case destination(PresentationAction) case view(View) + case fetchResponse(Result) public enum View { case onAppear @@ -66,10 +73,12 @@ public struct Schedule { Reduce { state, action in switch action { case .view(.onAppear): - state.day1 = try! dataClient.fetchDay1() - state.day2 = try! dataClient.fetchDay2() - state.workshop = try! dataClient.fetchWorkshop() - return .none + return .send(.fetchResponse(Result { + let day1 = try dataClient.fetchDay1() + let day2 = try dataClient.fetchDay2() + let workshop = try dataClient.fetchWorkshop() + return .init(day1: day1, day2: day2, workshop: workshop) + })) case let .view(.disclosureTapped(session)): guard let description = session.description, let speakers = session.speakers else { return .none @@ -93,6 +102,17 @@ public struct Schedule { #elseif os(visionOS) return .run { _ in await openURL(url) } #endif + case let .fetchResponse(.success(response)): + state.day1 = response.day1 + state.day2 = response.day2 + state.workshop = response.workshop + return .none + case let .fetchResponse(.failure(error as DecodingError)): + assertionFailure(error.localizedDescription) + return .none + case let .fetchResponse(.failure(error)): + print(error) // TODO: replace to Logger API + return .none case .binding, .path, .destination: return .none } diff --git a/MyLibrary/Tests/MyLibraryTests/MyLibraryTests.swift b/MyLibrary/Tests/MyLibraryTests/MyLibraryTests.swift deleted file mode 100644 index 1d87eb2..0000000 --- a/MyLibrary/Tests/MyLibraryTests/MyLibraryTests.swift +++ /dev/null @@ -1,13 +0,0 @@ -import XCTest - -@testable import MyLibrary - -final class MyLibraryTests: XCTestCase { - func testExample() throws { - // XCTest Documentation - // https://developer.apple.com/documentation/xctest - - // Defining Test Cases and Test Methods - // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods - } -} diff --git a/MyLibrary/Tests/ScheduleFeatureTests/Mocks.swift b/MyLibrary/Tests/ScheduleFeatureTests/Mocks.swift new file mode 100644 index 0000000..c54bee9 --- /dev/null +++ b/MyLibrary/Tests/ScheduleFeatureTests/Mocks.swift @@ -0,0 +1,97 @@ +import Foundation +import SharedModels + +extension Conference { + static let mock1 = Self( + id: 1, + title: "conference1", + date: Date(timeIntervalSince1970: 1_000), + schedules: [ + .init( + time: Date(timeIntervalSince1970: 10_000), + sessions: [ + .mock1, + .mock2, + ] + ) + ] + ) + + static let mock2 = Self( + id: 2, + title: "conference2", + date: Date(timeIntervalSince1970: 2_000), + schedules: [ + .init( + time: Date(timeIntervalSince1970: 20_000), + sessions: [ + .mock1, + .mock2 + ] + ) + ] + ) + + static let mock3 = Self( + id: 3, + title: "conference3", + date: Date(timeIntervalSince1970: 3_000), + schedules: [ + .init( + time: Date(timeIntervalSince1970: 30_000), + sessions: [ + .mock1, + .mock2 + ] + ) + ] + ) +} + +extension Session { + static let mock1 = Self( + title: "session1", + speakers: [ + .mock1 + ], + place: "place1", + description: "description1", + requirements: "requirements1" + ) + + static let mock2 = Self( + title: "session2", + speakers: [ + .mock2 + ], + place: "place2", + description: "description2", + requirements: "requirements2" + ) +} + +extension Speaker { + static let mock1 = Self( + name: "speaker1", + imageName: "image1", + bio: "bio1", + links: [ + .init( + name: "sns1", + url: URL(string: "https://example.com/speaker1")! + ) + ] + ) + + static let mock2 = Self( + name: "speaker2", + imageName: "image2", + bio: "bio2", + links: [ + .init( + name: "sns2", + url: URL(string: "https://example.com/speaker2")! + ) + ] + ) +} diff --git a/MyLibrary/Tests/ScheduleFeatureTests/ScheduleTests.swift b/MyLibrary/Tests/ScheduleFeatureTests/ScheduleTests.swift new file mode 100644 index 0000000..f0f0954 --- /dev/null +++ b/MyLibrary/Tests/ScheduleFeatureTests/ScheduleTests.swift @@ -0,0 +1,38 @@ +import ComposableArchitecture +import DataClient +import XCTest + +@testable import ScheduleFeature + +final class ScheduleTests: XCTestCase { + @MainActor + func testFetchData() async { + let store = TestStore(initialState: Schedule.State()) { + Schedule() + } withDependencies: { + $0[DataClient.self].fetchDay1 = { @Sendable in .mock1 } + $0[DataClient.self].fetchDay2 = { @Sendable in .mock2 } + $0[DataClient.self].fetchWorkshop = { @Sendable in .mock3 } + } + await store.send(.view(.onAppear)) + await store.receive(\.fetchResponse.success) { + $0.day1 = .mock1 + $0.day2 = .mock2 + $0.workshop = .mock3 + } + } + + @MainActor + func testFetchDataFailure() async { + struct FetchError: Equatable, Error {} + let store = TestStore(initialState: Schedule.State()) { + Schedule() + } withDependencies: { + $0[DataClient.self].fetchDay1 = { @Sendable in throw FetchError() } + $0[DataClient.self].fetchDay2 = { @Sendable in .mock2 } + $0[DataClient.self].fetchWorkshop = { @Sendable in .mock3 } + } + await store.send(.view(.onAppear)) + await store.receive(\.fetchResponse.failure) + } +}