diff --git a/App/Course App UITests/Course_App_UITests.swift b/App/Course App UITests/Course_App_UITests.swift index fe40e60..abeaaa5 100644 --- a/App/Course App UITests/Course_App_UITests.swift +++ b/App/Course App UITests/Course_App_UITests.swift @@ -8,28 +8,50 @@ import XCTest final class Course_App_UITests: XCTestCase { - + override func setUpWithError() throws { // Put setup code here. This method is called before the invocation of each test method in the class. - + // In UI tests it is usually best to stop immediately when a failure occurs. continueAfterFailure = false - + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. } - + override func tearDownWithError() throws { // Put teardown code here. This method is called after the invocation of each test method in the class. } - - func testExample() throws { + + func testSignInView() throws { // UI tests must launch the application that they test. let app = XCUIApplication() app.launch() - + + let collectionViewsQuery = app.collectionViews + collectionViewsQuery.textFields["Email"].tap() + app.keys["delete"].tap() + app.keys["t"].tap() + + collectionViewsQuery.secureTextFields["password"].tap() + app.keys["delete"].tap() + app.keys["t"].tap() + app.keys["e"].tap() + app.keys["s"].tap() + app.keys["t"].tap() + app.keys["more"].tap() + app.keys["1"].tap() + app.keys["2"].tap() + app.keys["3"].tap() + + XCUIApplication()/*@START_MENU_TOKEN@*/.buttons["Return"]/*[[".keyboards",".buttons[\"return\"]",".buttons[\"Return\"]"],[[[-1,2],[-1,1],[-1,0,1]],[[-1,2],[-1,1]]],[0]]@END_MENU_TOKEN@*/.tap() + + collectionViewsQuery/*@START_MENU_TOKEN@*/.buttons["SignIn"]/*[[".cells.buttons[\"SignIn\"]",".buttons[\"SignIn\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() + + + // Use XCTAssert and related functions to verify your tests produce the correct results. } - + func testLaunchPerformance() throws { if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { // This measures how long it takes to launch your application. diff --git a/App/Course App UnitTests/Course_App_UnitTests.swift b/App/Course App UnitTests/Course_App_UnitTests.swift deleted file mode 100644 index b4e8b75..0000000 --- a/App/Course App UnitTests/Course_App_UnitTests.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// Course_App_UnitTests.swift -// Course App UnitTests -// -// Created by Tomáš Duchoslav on 09.05.2024. -// - -import XCTest - -final class Course_App_UnitTests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - 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. - // Any test you write for XCTest can be annotated as throws and async. - // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. - // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. - } - - func testPerformanceExample() throws { - // This is an example of a performance test case. - measure { - // Put the code you want to measure the time of here. - } - } - -} diff --git a/App/Course App UnitTests/Endpoint/EndpointTests.swift b/App/Course App UnitTests/Endpoint/EndpointTests.swift new file mode 100644 index 0000000..a7d0b23 --- /dev/null +++ b/App/Course App UnitTests/Endpoint/EndpointTests.swift @@ -0,0 +1,67 @@ +// +// EndpointTests.swift +// Course App UnitTests +// +// Created by Tomáš Duchoslav on 20.06.2024. +// + +@testable import App_Course_Dev +import XCTest + +final class EndpointTests: XCTestCase { + + enum MockEndpoint: Endpoint { + case mockMethodGet + case mockMethodPost + case mockParameterGet + + var host: URL { + URL(string: "hhtps://example.com")! + } + + var path: String { + "api/test/path" + } + + var method: HTTPMethod { + switch self { + case .mockMethodGet, .mockParameterGet: + .get + case .mockMethodPost: + .post + } + } + } + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testHTTPMethod() throws { + guard let urlRequestGet = try? MockEndpoint.mockParameterGet.asURLRequest() else { + XCTFail("Can't create url request") + return + } + + XCTAssert(urlRequestGet.httpMethod == HTTPMethod.get.rawValue, "good") + + guard let urlRequestPost = try? MockEndpoint.mockMethodPost.asURLRequest() else { + XCTFail("Can't create url request") + return + } + + XCTAssert(urlRequestPost.httpMethod == HTTPMethod.post.rawValue, "good") + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/App/Course App UnitTests/Managers/MockAPIManager.swift b/App/Course App UnitTests/Managers/MockAPIManager.swift new file mode 100644 index 0000000..68dac60 --- /dev/null +++ b/App/Course App UnitTests/Managers/MockAPIManager.swift @@ -0,0 +1,32 @@ +// +// MockAPIManager.swift +// Course App UnitTests +// +// Created by Tomáš Duchoslav on 20.06.2024. +// + +@testable import App_Course_Dev +import Foundation + +enum MockError: Error { + case noMockDataError +} + +final class MockAPIManager: APIManaging { + var apiError: NetworkingError? + var mockData: Data? + + func request(_ endpoint: any Endpoint) async throws -> T where T : Decodable { + if let apiError{ + throw apiError + } + + if let data = mockData { + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .iso8601 + return try decoder.decode(T.self, from: data) + } else { + throw MockError.noMockDataError + } + } +} diff --git a/App/Course App UnitTests/Managers/MockKeychainManager.swift b/App/Course App UnitTests/Managers/MockKeychainManager.swift new file mode 100644 index 0000000..4f43bdf --- /dev/null +++ b/App/Course App UnitTests/Managers/MockKeychainManager.swift @@ -0,0 +1,32 @@ +// +// MockKeychainManager.swift +// Course App UnitTests +// +// Created by Tomáš Duchoslav on 20.06.2024. +// + +@testable import App_Course_Dev +import Foundation +import KeychainAccess + +final class MockKeychainManager: KeychainManaging { + let keychain = Keychain() + + func store(key: String, value: T) throws where T : Encodable { + keychain[data: key] = try JSONEncoder().encode(value) + } + + func fetch(key: String) throws -> T where T : Decodable { + guard let data = try keychain.getData(key) else { + throw KeychainManagerError.dataNotFound + } + + return try JSONDecoder().decode(T.self, from: data) + } + + func remove(key: String) throws { + try keychain.remove(key) + } + + +} diff --git a/App/Course App UnitTests/Model/MockDataResponses.swift b/App/Course App UnitTests/Model/MockDataResponses.swift new file mode 100644 index 0000000..a4d0fb7 --- /dev/null +++ b/App/Course App UnitTests/Model/MockDataResponses.swift @@ -0,0 +1,29 @@ +// +// MockDataResponses.swift +// Course App UnitTests +// +// Created by Tomáš Duchoslav on 20.06.2024. +// + +@testable import App_Course_Dev +import Foundation + +enum MockDataResponses { + static let mockJokeResponse = + """ + { + "id": "12345", + "categories": ["programming", "funny"], + "created_at": "2024-06-18T12:00:00Z", + "url": "https://api.exampe.com/jokes/12345", + "value": "Why do programmers prefer dark mode? Because light attracts bugs!" + } + """.data(using: .utf8)! + + static let mockCategoriesResponse = + """ + [ + "programming", "funny" + ] + """.data(using: .utf8)! +} diff --git a/App/Course App UnitTests/ServiceTests/JokeServiceTests.swift b/App/Course App UnitTests/ServiceTests/JokeServiceTests.swift new file mode 100644 index 0000000..e77af3c --- /dev/null +++ b/App/Course App UnitTests/ServiceTests/JokeServiceTests.swift @@ -0,0 +1,42 @@ +// +// JokeServiceTests.swift +// Course App UnitTests +// +// Created by Tomáš Duchoslav on 20.06.2024. +// + +@testable import App_Course_Dev +import XCTest + +final class JokeServiceTests: XCTestCase { + + var mockApiManager: MockAPIManager! + var jokeService: JokeService! + + override func setUpWithError() throws { + try super.setUpWithError() + mockApiManager = MockAPIManager() + jokeService = JokeService(apiManager: mockApiManager) + } + + override func tearDownWithError() throws { + mockApiManager = nil + jokeService = nil + try super.tearDownWithError() + } + + func testFetchCategories() async throws { + mockApiManager.mockData = MockDataResponses.mockCategoriesResponse + let categories = try await jokeService.loadCategories() + XCTAssert(categories.count == 2, "good") + XCTAssert(categories.contains(where: { $0 == "funny" })) + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/App/Course App UnitTests/ServiceTests/KeychainServiceTests.swift b/App/Course App UnitTests/ServiceTests/KeychainServiceTests.swift new file mode 100644 index 0000000..eef8dd5 --- /dev/null +++ b/App/Course App UnitTests/ServiceTests/KeychainServiceTests.swift @@ -0,0 +1,43 @@ +// +// KeychainServiceTests.swift +// Course App UnitTests +// +// Created by Tomáš Duchoslav on 20.06.2024. +// + +@testable import App_Course_Dev +import XCTest + +final class KeychainServiceTests: XCTestCase { + + var mockKeychainManager: MockKeychainManager! + var keychainService: KeychainService! + + override func setUpWithError() throws { + try super.setUpWithError() + mockKeychainManager = MockKeychainManager() + keychainService = KeychainService(keychainManager: mockKeychainManager) + } + + override func tearDownWithError() throws { + mockKeychainManager = nil + keychainService = nil + try super.tearDownWithError() + } + + func testStoreAuthData() throws { + try keychainService.storeAuthData(authData: "12345") + XCTAssert(try mockKeychainManager.keychain.contains("com.course.app.authData")) + + try keychainService.removeAuthData() + XCTAssert(try !mockKeychainManager.keychain.contains("com.course.app.authData")) + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/App/Course App.xcodeproj/project.pbxproj b/App/Course App.xcodeproj/project.pbxproj index 07889f3..8b049a5 100644 --- a/App/Course App.xcodeproj/project.pbxproj +++ b/App/Course App.xcodeproj/project.pbxproj @@ -15,7 +15,6 @@ A845D94C2BF3711D0039AA1D /* DataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A845D94B2BF3711D0039AA1D /* DataProvider.swift */; }; A845D9532BF377250039AA1D /* ReusableIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = A845D9522BF377250039AA1D /* ReusableIdentifier.swift */; }; A845D9552BF377A70039AA1D /* UICollectionView+ReusableIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = A845D9542BF377A70039AA1D /* UICollectionView+ReusableIdentifier.swift */; }; - A8830C1E2BED0315005CAFEA /* Course_App_UnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8830C1D2BED0315005CAFEA /* Course_App_UnitTests.swift */; }; A8830C2B2BED03BB005CAFEA /* Course_App_UITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8830C2A2BED03BB005CAFEA /* Course_App_UITests.swift */; }; A8830C2D2BED03BB005CAFEA /* Course_App_UITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8830C2C2BED03BB005CAFEA /* Course_App_UITestsLaunchTests.swift */; }; A8830C402BED0EE0005CAFEA /* BuildConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8830C3F2BED0EE0005CAFEA /* BuildConfiguration.swift */; }; @@ -110,6 +109,12 @@ F190AB342BFDE2D0001AF32D /* SwipingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F190AB332BFDE2D0001AF32D /* SwipingView.swift */; }; F190AB362BFDE300001AF32D /* SwipingCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = F190AB352BFDE300001AF32D /* SwipingCard.swift */; }; F190AB382BFDE372001AF32D /* ScratchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F190AB372BFDE372001AF32D /* ScratchView.swift */; }; + F1FFB19B2C24139B00BF481A /* EndpointTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FFB19A2C24139B00BF481A /* EndpointTests.swift */; }; + F1FFB19D2C2418AA00BF481A /* MockAPIManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FFB19C2C2418AA00BF481A /* MockAPIManager.swift */; }; + F1FFB19F2C24193C00BF481A /* MockDataResponses.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FFB19E2C24193C00BF481A /* MockDataResponses.swift */; }; + F1FFB1A12C241B1F00BF481A /* JokeServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FFB1A02C241B1F00BF481A /* JokeServiceTests.swift */; }; + F1FFB1A72C2420A200BF481A /* MockKeychainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FFB1A62C2420A200BF481A /* MockKeychainManager.swift */; }; + F1FFB1A92C242C9800BF481A /* KeychainServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1FFB1A82C242C9800BF481A /* KeychainServiceTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -140,7 +145,6 @@ A845D9522BF377250039AA1D /* ReusableIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReusableIdentifier.swift; sourceTree = ""; }; A845D9542BF377A70039AA1D /* UICollectionView+ReusableIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UICollectionView+ReusableIdentifier.swift"; sourceTree = ""; }; A8830C1B2BED0315005CAFEA /* Course App UnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Course App UnitTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - A8830C1D2BED0315005CAFEA /* Course_App_UnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Course_App_UnitTests.swift; sourceTree = ""; }; A8830C282BED03BB005CAFEA /* Course App UITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Course App UITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; A8830C2A2BED03BB005CAFEA /* Course_App_UITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Course_App_UITests.swift; sourceTree = ""; }; A8830C2C2BED03BB005CAFEA /* Course_App_UITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Course_App_UITestsLaunchTests.swift; sourceTree = ""; }; @@ -233,6 +237,12 @@ F190AB332BFDE2D0001AF32D /* SwipingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipingView.swift; sourceTree = ""; }; F190AB352BFDE300001AF32D /* SwipingCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipingCard.swift; sourceTree = ""; }; F190AB372BFDE372001AF32D /* ScratchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScratchView.swift; sourceTree = ""; }; + F1FFB19A2C24139B00BF481A /* EndpointTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndpointTests.swift; sourceTree = ""; }; + F1FFB19C2C2418AA00BF481A /* MockAPIManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAPIManager.swift; sourceTree = ""; }; + F1FFB19E2C24193C00BF481A /* MockDataResponses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDataResponses.swift; sourceTree = ""; }; + F1FFB1A02C241B1F00BF481A /* JokeServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JokeServiceTests.swift; sourceTree = ""; }; + F1FFB1A62C2420A200BF481A /* MockKeychainManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockKeychainManager.swift; sourceTree = ""; }; + F1FFB1A82C242C9800BF481A /* KeychainServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainServiceTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -375,7 +385,10 @@ A8830C1C2BED0315005CAFEA /* Course App UnitTests */ = { isa = PBXGroup; children = ( - A8830C1D2BED0315005CAFEA /* Course_App_UnitTests.swift */, + F1FFB1A52C24208400BF481A /* Endpoint */, + F1FFB1A42C24207700BF481A /* Model */, + F1FFB1A32C24206D00BF481A /* Managers */, + F1FFB1A22C24205900BF481A /* ServiceTests */, ); path = "Course App UnitTests"; sourceTree = ""; @@ -760,6 +773,40 @@ path = ViewModifiers; sourceTree = ""; }; + F1FFB1A22C24205900BF481A /* ServiceTests */ = { + isa = PBXGroup; + children = ( + F1FFB1A02C241B1F00BF481A /* JokeServiceTests.swift */, + F1FFB1A82C242C9800BF481A /* KeychainServiceTests.swift */, + ); + path = ServiceTests; + sourceTree = ""; + }; + F1FFB1A32C24206D00BF481A /* Managers */ = { + isa = PBXGroup; + children = ( + F1FFB19C2C2418AA00BF481A /* MockAPIManager.swift */, + F1FFB1A62C2420A200BF481A /* MockKeychainManager.swift */, + ); + path = Managers; + sourceTree = ""; + }; + F1FFB1A42C24207700BF481A /* Model */ = { + isa = PBXGroup; + children = ( + F1FFB19E2C24193C00BF481A /* MockDataResponses.swift */, + ); + path = Model; + sourceTree = ""; + }; + F1FFB1A52C24208400BF481A /* Endpoint */ = { + isa = PBXGroup; + children = ( + F1FFB19A2C24139B00BF481A /* EndpointTests.swift */, + ); + path = Endpoint; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -1004,7 +1051,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - A8830C1E2BED0315005CAFEA /* Course_App_UnitTests.swift in Sources */, + F1FFB19F2C24193C00BF481A /* MockDataResponses.swift in Sources */, + F1FFB1A12C241B1F00BF481A /* JokeServiceTests.swift in Sources */, + F1FFB1A72C2420A200BF481A /* MockKeychainManager.swift in Sources */, + F1FFB1A92C242C9800BF481A /* KeychainServiceTests.swift in Sources */, + F1FFB19B2C24139B00BF481A /* EndpointTests.swift in Sources */, + F1FFB19D2C2418AA00BF481A /* MockAPIManager.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };