├── Assertly ├── Resources │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ └── Base.lproj │ │ └── LaunchScreen.storyboard ├── Utils │ ├── AppConstants.swift │ ├── NetworkError.swift │ ├── TestConfiguration.swift │ ├── extension+XCTestCase.swift │ ├── Extension+Array.swift │ └── AssertUnitTestable.swift ├── AssertlyMain │ ├── NetworkingBaseTest │ │ ├── AssertlyNetworkingProtocol.swift │ │ ├── AssertlyMockNetworking.swift │ │ ├── ErrorHandlingTests.swift │ │ └── AssertlyNetworkingTests.swift │ └── ViewModelBaseTest │ │ ├── AssertlyViewModelProtocol.swift │ │ ├── MockViewModel.swift │ │ └── AssertlyViewModelTests.swift ├── Info.plist ├── SampleApp │ ├── Model │ │ └── Users.swift │ ├── ViewModel │ │ └── SampleViewModel.swift │ └── ViewController │ │ └── MockSampleViewController.swift └── App │ ├── AppDelegate.swift │ └── SceneDelegate.swift ├── AssertlyInUse.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved ├── xcuserdata │ └── sharon-omoyeni.xcuserdatad │ │ ├── xcschemes │ │ └── xcschememanagement.plist │ │ └── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist ├── xcshareddata │ └── xcschemes │ │ └── AssertlyInUse.xcscheme └── project.pbxproj ├── AssertlyUITests ├── AssertlyUITestsLaunchTests.swift └── AssertlyUITests.swift ├── AssertlyTests ├── AssertlyTests.swift ├── SampleNetworkingTest │ └── NetworkingTests.swift ├── ErrorHandlingTests │ └── ErrorHandlingTests.swift ├── SampleUseViewModelTest │ └── AssertlySampleUserViewModelTests.swift └── SampleControllerTest │ └── AssertlySampleViewControllerTest.swift └── README.md /Assertly/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AssertlyInUse.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Assertly/Resources/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Assertly/Utils/AppConstants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppConstants.swift 3 | // Assertly 4 | // 5 | // Created by Sharon Omoyeni Babatunde on 27/07/2024. 6 | // 7 | 8 | import Foundation 9 | 10 | enum AppConstants { 11 | static let BASE_URL = infoPlistString(key: "BASE_URL") 12 | } 13 | -------------------------------------------------------------------------------- /Assertly/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Assertly/Utils/NetworkError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkError.swift 3 | // Assertly 4 | // 5 | // Created by Sharon Omoyeni Babatunde on 25/11/2023. 6 | // 7 | 8 | import Foundation 9 | 10 | enum NetworkError: Error { 11 | case invalidURL 12 | case noData 13 | case decodingError 14 | } 15 | -------------------------------------------------------------------------------- /AssertlyInUse.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Assertly/AssertlyMain/NetworkingBaseTest/AssertlyNetworkingProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssertlyNetworkingProtocol.swift 3 | // Assertly 4 | // 5 | // Created by Sharon Omoyeni Babatunde on 06/07/2024. 6 | // 7 | 8 | import Foundation 9 | 10 | public protocol NetworkingProtocol { 11 | func request(_ endpoint: String, completion: @escaping (Result) -> Void) 12 | } 13 | 14 | -------------------------------------------------------------------------------- /Assertly/AssertlyMain/ViewModelBaseTest/AssertlyViewModelProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssertlyViewModelProtocol.swift 3 | // Assertly 4 | // 5 | // Created by Sharon Omoyeni Babatunde on 06/07/2024. 6 | // 7 | 8 | import Foundation 9 | 10 | public protocol ViewModelProtocol { 11 | 12 | var title: String { get } 13 | var mockResults: [String: Any] { get } 14 | var mockErrors: [String: Error] { get } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Assertly/Utils/TestConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestConfiguration.swift 3 | // Assertly 4 | // 5 | // Created by Sharon Omoyeni Babatunde on 25/11/2023. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct TestConfiguration { 11 | static var baseURL = "https://api.example.com" 12 | static var apiKey = "test_api_key" 13 | 14 | public static func configure(baseURL: String, apiKey: String) { 15 | self.baseURL = baseURL 16 | self.apiKey = apiKey 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Assertly/Utils/extension+XCTestCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // extension+XCTestCase.swift 3 | // Assertly 4 | // 5 | // Created by Sharon Omoyeni Babatunde on 25/11/2023. 6 | // 7 | 8 | import Foundation 9 | import XCTest 10 | 11 | public extension XCTestCase { 12 | func waitForAsyncOperation(_ timeout: TimeInterval = 5, operation: @escaping (@escaping () -> Void) -> Void) { 13 | let expectation = self.expectation(description: "Async operation") 14 | operation { 15 | expectation.fulfill() 16 | } 17 | waitForExpectations(timeout: timeout, handler: nil) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Assertly/AssertlyMain/NetworkingBaseTest/AssertlyMockNetworking.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockNetworking.swift 3 | // Assertly 4 | // 5 | // Created by Sharon Omoyeni Babatunde on 06/03/2024. 6 | // 7 | 8 | import Foundation 9 | 10 | public class MockNetworking: NetworkingProtocol { 11 | var mockResult: Result? 12 | 13 | public func request(_ endpoint: String, completion: @escaping (Result) -> Void) { 14 | guard let result = mockResult as? Result else { 15 | fatalError("Mock result not set or of incorrect type") 16 | } 17 | completion(result) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Assertly/Utils/Extension+Array.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extension+Array.swift 3 | // Assertly 4 | // 5 | // Created by Sharon Omoyeni Babatunde on 25/11/2023. 6 | // 7 | 8 | import Foundation 9 | 10 | var infoPlistProperty: [String: Any]? { 11 | if let path = Bundle.main.path(forResource: "Info", ofType: "plist"), let xml = FileManager.default.contents(atPath: path) { 12 | return (try? PropertyListSerialization.propertyList(from: xml, options: .mutableContainersAndLeaves, format: nil)) as? [String: Any] 13 | } 14 | return nil 15 | } 16 | 17 | func infoPlistString(key: String) -> String { 18 | infoPlistProperty![key] as! String 19 | } 20 | -------------------------------------------------------------------------------- /Assertly/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BASE_URL 6 | $(BASE_URL) 7 | UIApplicationSceneManifest 8 | 9 | UIApplicationSupportsMultipleScenes 10 | 11 | UISceneConfigurations 12 | 13 | UIWindowSceneSessionRoleApplication 14 | 15 | 16 | UISceneConfigurationName 17 | Default Configuration 18 | UISceneDelegateClassName 19 | $(PRODUCT_MODULE_NAME).SceneDelegate 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /AssertlyInUse.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "6e6e2584c46b7f7c049c81c278ac6b3aba14d368f4258016463f5b12e22641ee", 3 | "pins" : [ 4 | { 5 | "identity" : "assertly", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/yeniObabatunde/Assertly.git", 8 | "state" : { 9 | "branch" : "main", 10 | "revision" : "1f3f207da7446cdfc3f98999a7d8979bc8db02cf" 11 | } 12 | }, 13 | { 14 | "identity" : "networkhandlerpackage", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/yeniObabatunde/NetworkHandlerPackage.git", 17 | "state" : { 18 | "branch" : "main", 19 | "revision" : "d1e111241811be77aa4f6e7010895f8c3c214e66" 20 | } 21 | } 22 | ], 23 | "version" : 3 24 | } 25 | -------------------------------------------------------------------------------- /Assertly/SampleApp/Model/Users.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Users.swift 3 | // Assertly 4 | // 5 | // Created by Sharon Omoyeni Babatunde on 06/07/2024. 6 | // 7 | 8 | import Foundation 9 | 10 | struct User: Equatable { 11 | let id: Int 12 | let name: String 13 | } 14 | 15 | struct UserList: Codable, Equatable { 16 | var totalPages: Int? 17 | var data: [UserListDatum]? 18 | 19 | enum CodingKeys: String, CodingKey { 20 | case totalPages = "total_pages" 21 | case data 22 | } 23 | } 24 | 25 | struct UserListDatum: Codable, Equatable { 26 | var id: Int? 27 | var email, firstName, lastName: String? 28 | var avatar: String? 29 | 30 | enum CodingKeys: String, CodingKey { 31 | case id, email 32 | case firstName = "first_name" 33 | case lastName = "last_name" 34 | case avatar 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /AssertlyInUse.xcodeproj/xcuserdata/sharon-omoyeni.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | AssertlyInUse.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 5861BB872C38CCC2001382DB 16 | 17 | primary 18 | 19 | 20 | 5861BB9D2C38CCC5001382DB 21 | 22 | primary 23 | 24 | 25 | 5861BBA72C38CCC5001382DB 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /AssertlyUITests/AssertlyUITestsLaunchTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssertlyUITestsLaunchTests.swift 3 | // AssertlyUITests 4 | // 5 | // Created by Sharon Omoyeni Babatunde on 12/10/2022. 6 | // 7 | 8 | import XCTest 9 | 10 | final class AssertlyUITestsLaunchTests: XCTestCase { 11 | 12 | override class var runsForEachTargetApplicationUIConfiguration: Bool { 13 | true 14 | } 15 | 16 | override func setUpWithError() throws { 17 | continueAfterFailure = false 18 | } 19 | 20 | func testLaunch() throws { 21 | let app = XCUIApplication() 22 | app.launch() 23 | 24 | // Insert steps here to perform after app launch but before taking a screenshot, 25 | // such as logging into a test account or navigating somewhere in the app 26 | 27 | let attachment = XCTAttachment(screenshot: app.screenshot()) 28 | attachment.name = "Launch Screen" 29 | attachment.lifetime = .keepAlways 30 | add(attachment) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Assertly/SampleApp/ViewModel/SampleViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SampleViewModel.swift 3 | // Assertly 4 | // 5 | // Created by Sharon Omoyeni Babatunde on 06/07/2023. 6 | // 7 | 8 | import Foundation 9 | import NetworkHandling 10 | 11 | class AssertlySampleUseViewModel { 12 | 13 | private var networkHandler: NetworkHandling 14 | var userList: ((UserList?) -> Void)? 15 | var onError: ((Error) -> Void)? 16 | 17 | init(networkHandler: NetworkHandling) { 18 | self.networkHandler = networkHandler 19 | } 20 | 21 | func getUser() { 22 | networkHandler.request(with: AppConstants.BASE_URL, method: .GET, body: nil, headers: nil) { [weak self] (result: Result) in 23 | 24 | DispatchQueue.main.async { 25 | switch result { 26 | case .success(let user): 27 | self?.userList?(user) 28 | Logger.printIfDebug(data: "\(user): DATA GOTTEN SUCCESSFULLY", logType: .success) 29 | case .failure(let error): 30 | self?.onError?(error) 31 | } 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Assertly/Utils/AssertUnitTestable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UnitTestableHelpers.swift 3 | // Assertly 4 | // 5 | // Created by Sharon Omoyeni Babatunde on 25/11/2023. 6 | // 7 | 8 | import XCTest 9 | 10 | // MARK: Core Framework Structure 11 | /* 12 | This structure provides a foundation for our unit tests. The UnitTestable protocol ensures that all test cases have a consistent interface, while the BaseTestCase class handles setup and teardown. 13 | */ 14 | 15 | public protocol UnitTestable { 16 | associatedtype Dependencies 17 | init(dependencies: Dependencies) 18 | func setUp() 19 | func tearDown() 20 | } 21 | 22 | 23 | public class BaseTestCase: XCTestCase { 24 | public var sut: T? 25 | 26 | public override func setUp() { 27 | super.setUp() 28 | sut = T(dependencies: createDependencies()) 29 | sut?.setUp() 30 | } 31 | 32 | public override func tearDown() { 33 | sut?.tearDown() 34 | sut = nil 35 | super.tearDown() 36 | } 37 | 38 | public func createDependencies() -> T.Dependencies { 39 | fatalError("Subclasses must implement createDependencies()") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /AssertlyTests/AssertlyTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssertlyTests.swift 3 | // AssertlyTests 4 | // 5 | // Created by Sharon Omoyeni Babatunde on 12/10/2022. 6 | // 7 | 8 | import XCTest 9 | @testable import Assertly 10 | 11 | final class AssertlyTests: XCTestCase { 12 | 13 | override func setUpWithError() throws { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | } 16 | 17 | override func tearDownWithError() throws { 18 | // Put teardown code here. This method is called after the invocation of each test method in the class. 19 | } 20 | 21 | func testExample() throws { 22 | // This is an example of a functional test case. 23 | // Use XCTAssert and related functions to verify your tests produce the correct results. 24 | // Any test you write for XCTest can be annotated as throws and async. 25 | // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. 26 | // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. 27 | } 28 | 29 | func testPerformanceExample() throws { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Assertly/AssertlyMain/NetworkingBaseTest/ErrorHandlingTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ErrorHandlingTests.swift 3 | // AssertlyTests 4 | // 5 | // Created by Sharon Omoyeni Babatunde on 20/08/2023. 6 | // 7 | 8 | import XCTest 9 | @testable import Assertly 10 | 11 | class ErrorHandlingTests: BaseTestCase, UnitTestable { 12 | var networking: MockNetworking! 13 | 14 | required init(dependencies: MockNetworking) { 15 | super.init() 16 | self.networking = dependencies 17 | } 18 | 19 | override func setUp() { 20 | super.setUp() 21 | // Additional setup if needed 22 | } 23 | 24 | override func tearDown() { 25 | // Additional teardown if needed 26 | super.tearDown() 27 | } 28 | 29 | override func createDependencies() -> MockNetworking { 30 | return MockNetworking() 31 | } 32 | 33 | func testInvalidURLError() { 34 | networking.mockResult = .failure(NetworkError.invalidURL) 35 | 36 | waitForAsyncOperation { done in 37 | self.networking.request("invalidEndpoint") { (result: Result<[String: String], Error>) in 38 | switch result { 39 | case .success: 40 | XCTFail("Expected failure, got success") 41 | case .failure(let error): 42 | XCTAssertEqual(error as? NetworkError, .invalidURL) 43 | } 44 | done() 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Assertly/App/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Assertly 4 | // 5 | // Created by Sharon Omoyeni Babatunde on 06/07/2022. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 15 | // Override point for customization after application launch. 16 | return true 17 | } 18 | 19 | // MARK: UISceneSession Lifecycle 20 | 21 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 22 | // Called when a new scene session is being created. 23 | // Use this method to select a configuration to create the new scene with. 24 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 25 | } 26 | 27 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 28 | // Called when the user discards a scene session. 29 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 30 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 31 | } 32 | 33 | 34 | } 35 | 36 | -------------------------------------------------------------------------------- /AssertlyTests/SampleNetworkingTest/NetworkingTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkingTests.swift 3 | // AssertlyTests 4 | // 5 | // Created by Sharon Omoyeni Babatunde on 29/07/2024. 6 | // 7 | 8 | import XCTest 9 | import Assertly 10 | @testable import AssertlyInUse 11 | 12 | class NetworkingTests: BaseTestCase, UnitTestable { 13 | 14 | var networking: MockNetworking! 15 | 16 | required init(dependencies: MockNetworking) { 17 | super.init() 18 | self.networking = dependencies 19 | } 20 | 21 | override func setUp() { 22 | super.setUp() 23 | } 24 | 25 | override func tearDown() { 26 | super.tearDown() 27 | } 28 | 29 | override func createDependencies() -> MockNetworking { 30 | return MockNetworking() 31 | } 32 | 33 | func testSuccessfulRequest() { 34 | let expectation = self.expectation(description: "Successful request") 35 | let mockData = ["key": "value"] 36 | networking.mockResult = .success(mockData) 37 | 38 | networking.request("testEndpoint") { (result: Result<[String: String], Error>) in 39 | switch result { 40 | case .success(let data): 41 | XCTAssertEqual(data, mockData) 42 | case .failure: 43 | XCTFail("Expected success, got failure") 44 | } 45 | expectation.fulfill() 46 | } 47 | 48 | waitForExpectations(timeout: 1, handler: nil) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /AssertlyTests/ErrorHandlingTests/ErrorHandlingTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ErrorHandlingTests.swift 3 | // AssertlyTests 4 | // 5 | // Created by Sharon Omoyeni Babatunde on 29/07/2024. 6 | // 7 | 8 | import XCTest 9 | import Assertly 10 | @testable import AssertlyInUse 11 | 12 | class ErrorHandlingTests: BaseTestCase, UnitTestable { 13 | var networking: MockNetworking! 14 | 15 | required init(dependencies: MockNetworking) { 16 | super.init() 17 | self.networking = dependencies 18 | } 19 | 20 | override func setUp() { 21 | super.setUp() 22 | // Additional setup if needed 23 | } 24 | 25 | override func tearDown() { 26 | // Additional teardown if needed 27 | super.tearDown() 28 | } 29 | 30 | override func createDependencies() -> MockNetworking { 31 | return MockNetworking() 32 | } 33 | 34 | func testInvalidURLError() { 35 | networking.mockResult = .failure(NetworkError.invalidURL) 36 | 37 | waitForAsyncOperation { done in 38 | self.networking.request("invalidEndpoint") { (result: Result<[String: String], Error>) in 39 | switch result { 40 | case .success: 41 | XCTFail("Expected failure, got success") 42 | case .failure(let error): 43 | XCTAssertEqual(error as? NetworkError, .invalidURL) 44 | } 45 | done() 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /AssertlyUITests/AssertlyUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssertlyUITests.swift 3 | // AssertlyUITests 4 | // 5 | // Created by Sharon Omoyeni Babatunde on 12/10/2022. 6 | // 7 | 8 | import XCTest 9 | 10 | final class AssertlyUITests: XCTestCase { 11 | 12 | override func setUpWithError() throws { 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | 15 | // In UI tests it is usually best to stop immediately when a failure occurs. 16 | continueAfterFailure = false 17 | 18 | // 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. 19 | } 20 | 21 | override func tearDownWithError() throws { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | } 24 | 25 | func testExample() throws { 26 | // UI tests must launch the application that they test. 27 | let app = XCUIApplication() 28 | app.launch() 29 | 30 | // Use XCTAssert and related functions to verify your tests produce the correct results. 31 | } 32 | 33 | func testLaunchPerformance() throws { 34 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { 35 | // This measures how long it takes to launch your application. 36 | measure(metrics: [XCTApplicationLaunchMetric()]) { 37 | XCUIApplication().launch() 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Assertly/AssertlyMain/NetworkingBaseTest/AssertlyNetworkingTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkingTests.swift 3 | // AssertlyTests 4 | // 5 | // Created by Sharon Omoyeni Babatunde on 06/01/2024. 6 | // 7 | 8 | 9 | import XCTest 10 | @testable import Assertly 11 | 12 | class NetworkingTests: BaseTestCase, UnitTestable { 13 | 14 | var networking: MockNetworking! 15 | 16 | required init(dependencies: MockNetworking) { 17 | super.init() 18 | self.networking = dependencies 19 | } 20 | 21 | override func setUp() { 22 | super.setUp() 23 | // Additional setup if needed 24 | } 25 | 26 | override func tearDown() { 27 | // Additional teardown if needed 28 | super.tearDown() 29 | } 30 | 31 | override func createDependencies() -> MockNetworking { 32 | return MockNetworking() 33 | } 34 | 35 | func testSuccessfulRequest() { 36 | let expectation = self.expectation(description: "Successful request") 37 | let mockData = ["key": "value"] 38 | networking.mockResult = .success(mockData) 39 | 40 | networking.request("testEndpoint") { (result: Result<[String: String], Error>) in 41 | switch result { 42 | case .success(let data): 43 | XCTAssertEqual(data, mockData) 44 | case .failure: 45 | XCTFail("Expected success, got failure") 46 | } 47 | expectation.fulfill() 48 | } 49 | 50 | waitForExpectations(timeout: 1, handler: nil) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Assertly/Resources/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /AssertlyTests/SampleUseViewModelTest/AssertlySampleUserViewModelTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssertlySampleUserViewModelTests.swift 3 | // AssertlyTests 4 | // 5 | // Created by Sharon Omoyeni Babatunde on 06/02/2023. 6 | // 7 | 8 | import XCTest 9 | import Assertly 10 | @testable import AssertlyInUse 11 | 12 | final class AssertlySampleUserViewModelTests: AssertlyViewModelTests { 13 | 14 | override func createDependencies() -> MockViewModel { 15 | return MockViewModel() 16 | } 17 | 18 | func testFetchUser() { 19 | let mockUser = User(id: 1, name: "John Doe") 20 | sut?.setMockResult(mockUser, for: "fetchUser") 21 | 22 | testAction("fetchUser", expectedResult: mockUser) 23 | } 24 | 25 | func testFetchUsers() { 26 | let mockUsers = [User(id: 1, name: "John"), User(id: 2, name: "Jane")] 27 | (sut)?.setMockArrayResult(mockUsers, for: "fetchUsers") 28 | 29 | testArrayAction("fetchUsers", expectedResult: mockUsers) 30 | } 31 | 32 | func testFetchUserDict() { 33 | let mockUserDict = [1: User(id: 1, name: "John"), 2: User(id: 2, name: "Jane")] 34 | (sut)?.setMockDictionaryResult(mockUserDict, for: "fetchUserDict") 35 | 36 | testDictionaryAction("fetchUserDict", expectedResult: mockUserDict) 37 | } 38 | 39 | func testFetchUserError() { 40 | let mockError = NSError(domain: "UserError", code: 404, userInfo: [NSLocalizedDescriptionKey: "User not found"]) 41 | (sut)?.setMockError(mockError, for: "fetchUser") 42 | 43 | testActionError("fetchUser", expectedError: mockError, as: NSError.self) 44 | } 45 | 46 | func testFetchUserFailure() { 47 | //MARK: This should fail because expected result doesn't equal actual result 48 | let expectedUser = User(id: 1, name: "John Doe") 49 | let actualUser = User(id: 2, name: "Jane Smith") // Different user 50 | sut?.setMockResult(actualUser, for: "fetchUser") 51 | assertNotEqual(actualUser, expectedUser) 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /Assertly/App/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Assertly 4 | // 5 | // Created by Sharon Omoyeni Babatunde on 06/07/2022. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 15 | if let windowScene = scene as? UIWindowScene { 16 | let window = UIWindow(windowScene: windowScene) 17 | window.rootViewController = MockSampleViewController() 18 | self.window = window 19 | window.makeKeyAndVisible() 20 | } 21 | 22 | } 23 | 24 | func sceneDidDisconnect(_ scene: UIScene) { 25 | // Called as the scene is being released by the system. 26 | // This occurs shortly after the scene enters the background, or when its session is discarded. 27 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 28 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 29 | } 30 | 31 | func sceneDidBecomeActive(_ scene: UIScene) { 32 | // Called when the scene has moved from an inactive state to an active state. 33 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 34 | } 35 | 36 | func sceneWillResignActive(_ scene: UIScene) { 37 | // Called when the scene will move from an active state to an inactive state. 38 | // This may occur due to temporary interruptions (ex. an incoming phone call). 39 | } 40 | 41 | func sceneWillEnterForeground(_ scene: UIScene) { 42 | // Called as the scene transitions from the background to the foreground. 43 | // Use this method to undo the changes made on entering the background. 44 | } 45 | 46 | func sceneDidEnterBackground(_ scene: UIScene) { 47 | // Called as the scene transitions from the foreground to the background. 48 | // Use this method to save data, release shared resources, and store enough scene-specific state information 49 | // to restore the scene back to its current state. 50 | } 51 | 52 | 53 | } 54 | 55 | -------------------------------------------------------------------------------- /AssertlyInUse.xcodeproj/xcuserdata/sharon-omoyeni.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 21 | 22 | 36 | 37 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /AssertlyTests/SampleControllerTest/AssertlySampleViewControllerTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AssertlySampleViewControllerTest.swift 3 | // Assertly 4 | // 5 | // Created by Sharon Omoyeni Babatunde on 06/07/2024. 6 | // 7 | 8 | import XCTest 9 | import Assertly 10 | @testable import AssertlyInUse 11 | 12 | class AssertlySampleViewControllerTest: AssertlyViewModelTests { 13 | 14 | var viewController: MockSampleViewController! 15 | 16 | override func createDependencies() -> MockViewModel { 17 | return MockViewModel() 18 | } 19 | 20 | override func setUp() { 21 | super.setUp() 22 | viewController = MockSampleViewController() 23 | viewController.loadViewIfNeeded() 24 | } 25 | 26 | override func tearDown() { 27 | viewController = nil 28 | super.tearDown() 29 | } 30 | 31 | func testUpdateUI_WithValidUser() { 32 | //MARK: Given 33 | let expectation = self.expectation(description: "Fetch User Success") 34 | let expectedUser = UserListDatum(id: 1, email: "john@example.com", firstName: "John", lastName: "Doe", avatar: "avatar_url") 35 | sut?.setMockResult(expectedUser, for: "getUser") 36 | 37 | //MARK: When 38 | viewController.fetchUser() 39 | 40 | //MARK: Then 41 | self.assertTrue(self.viewController.fetchUserCalled) 42 | assertNotEqual(expectedUser.email, "johnexample@gmail.com") 43 | expectation.fulfill() 44 | 45 | waitForExpectations(timeout: 1, handler: nil) 46 | } 47 | 48 | func testFetchUser_Failure() { 49 | //MARK: Given 50 | let expectedError = NSError(domain: "TestError", code: 404, userInfo: [NSLocalizedDescriptionKey: "User not found"]) 51 | sut?.setMockError(expectedError, for: "getUser") 52 | 53 | //MARK: When 54 | viewController.fetchUser() 55 | 56 | //MARK: Then 57 | assertTrue(viewController.fetchUserCalled) 58 | if let alert = viewController.presentedAlert { 59 | assertEqual(alert.title, "Error", "Alert title should be 'Error'") 60 | assertEqual(alert.message, "User not found", "Alert message should match the error description") 61 | } 62 | } 63 | 64 | func testUpdateUI_WithNilID() { 65 | //MARK: Given 66 | let user = UserListDatum(id: nil, email: "no-id@example.com", firstName: "No", lastName: "ID", avatar: "avatar_url") 67 | 68 | //MARK: When 69 | viewController.updateUI(with: user) 70 | 71 | //MARK: Then 72 | assertEqual(viewController.idLabel.text, "ID: 0", "ID label should display default ID when nil") 73 | } 74 | 75 | func testHandleError_DisplaysAlert() { 76 | //MARK: Given 77 | let error = NSError(domain: "TestError", code: 500, userInfo: [NSLocalizedDescriptionKey: "Server error"]) 78 | 79 | //MARK: When 80 | viewController.handle(error: error) 81 | 82 | //MARK: Then 83 | if let alert = assertNotNilAndUnwrap(viewController.presentedAlert, "Error alert should be presented") { 84 | assertEqual(alert.title, "Error", "Alert title should be 'Error'") 85 | assertEqual(alert.message, "Server error", "Alert message should match the error description") 86 | } 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /Assertly/AssertlyMain/ViewModelBaseTest/MockViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockViewModel.swift 3 | // AssertlyTests 4 | // 5 | // Created by Sharon Omoyeni Babatunde on 06/07/2024. 6 | // 7 | 8 | import Foundation 9 | 10 | public class MockViewModel: ViewModelProtocol { 11 | 12 | public var title: String = "Mock Title" 13 | public var mockResults: [String: Any] = [:] 14 | public var mockErrors: [String: Error] = [:] 15 | 16 | public func performAction(_ action: String, completion: @escaping (Result) -> Void) { 17 | if let error = mockErrors[action] { 18 | completion(.failure(error)) 19 | } else if let result = mockResults[action] as? T { 20 | completion(.success(result)) 21 | } else { 22 | fatalError("No mock result or error set for action: \(action)") 23 | } 24 | } 25 | 26 | public func performAction(_ action: String, completion: @escaping (Result) -> Void) { 27 | if let result = mockResults[action] as? R { 28 | completion(.success(result)) 29 | } else if let error = mockErrors[action] { 30 | completion(.failure(error)) 31 | } else { 32 | fatalError("No mock result or error set for action: \(action)") 33 | } 34 | } 35 | 36 | public func setMockResult(_ result: R, for action: String) { 37 | mockResults[action] = result 38 | } 39 | 40 | public func setMockResult(_ result: R, for action: String) { 41 | mockResults[action] = result 42 | } 43 | 44 | public func setMockError(_ error: Error, for action: String) { 45 | mockErrors[action] = error 46 | } 47 | 48 | // Helper methods for common types 49 | public func setMockArrayResult(_ result: [R], for action: String) { 50 | mockResults[action] = result 51 | } 52 | 53 | public func setMockArrayResult(_ result: [R], for action: String) { 54 | mockResults[action] = result 55 | } 56 | 57 | public func setMockDictionaryResult(_ result: [U: Any], for action: String) { 58 | mockResults[action] = result 59 | } 60 | 61 | public func setMockDictionaryResult(_ result: [U: Any], for action: String) { 62 | mockResults[action] = result 63 | } 64 | } 65 | 66 | //MARK: Extension to handle array results 67 | public extension MockViewModel { 68 | 69 | func performArrayAction(_ action: String, completion: @escaping (Result<[R], Error>) -> Void) { 70 | if let result = mockResults[action] as? [R] { 71 | completion(.success(result)) 72 | } else if let error = mockErrors[action] { 73 | completion(.failure(error)) 74 | } else { 75 | fatalError("No mock array result or error set for action: \(action)") 76 | } 77 | } 78 | 79 | func performArrayAction(_ action: String, completion: @escaping (Result<[R], Error>) -> Void) { 80 | if let result = mockResults[action] as? [R] { 81 | completion(.success(result)) 82 | } else if let error = mockErrors[action] { 83 | completion(.failure(error)) 84 | } else { 85 | fatalError("No mock array result or error set for action: \(action)") 86 | } 87 | } 88 | 89 | } 90 | 91 | //MARK: Extension to handle dictionary results 92 | public extension MockViewModel { 93 | 94 | func performDictionaryAction(_ action: String, completion: @escaping (Result<[Key: Value], Error>) -> Void) { 95 | if let result = mockResults[action] as? [Key: Value] { 96 | completion(.success(result)) 97 | } else if let error = mockErrors[action] { 98 | completion(.failure(error)) 99 | } else { 100 | fatalError("No mock dictionary result or error set for action: \(action)") 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Assertly/SampleApp/ViewController/MockSampleViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockSampleViewController.swift 3 | // Assertly 4 | // 5 | // Created by Sharon Omoyeni Babatunde on 06/07/2024. 6 | // 7 | 8 | import UIKit 9 | import NetworkHandling 10 | //import Assertly 11 | 12 | class MockSampleViewController: UIViewController { 13 | 14 | //MARK: UI ELEMENT 15 | lazy var nameLabel = UILabel() 16 | lazy var idLabel = UILabel() 17 | var presentedAlert: UIAlertController? 18 | 19 | lazy var actionButton: UIButton = { 20 | let button = UIButton(type: .system) 21 | button.backgroundColor = .purple 22 | button.layer.cornerRadius = 12 23 | button.setTitle("Perform any action", for: .normal) 24 | button.translatesAutoresizingMaskIntoConstraints = false 25 | return button 26 | }() 27 | 28 | // private var mockViewModel: MockViewModel 29 | private let networkHandler: NetworkHandler = NetworkHandler() 30 | private var viewModel: AssertlySampleUseViewModel? 31 | var fetchUserCalled: Bool = false 32 | // 33 | // init(mockViewModel: MockViewModel) { 34 | // 35 | // super.init(nibName: nil, bundle: nil) 36 | // } 37 | // 38 | // required init?(coder: NSCoder) { 39 | // fatalError("init(coder:) has not been implemented") 40 | // } 41 | // 42 | override func viewDidLoad() { 43 | super.viewDidLoad() 44 | setupUI() 45 | onCompletion() 46 | self.viewModel = AssertlySampleUseViewModel(networkHandler: networkHandler) 47 | } 48 | 49 | private func setupUI() { 50 | title = "Mock Sample" 51 | view.backgroundColor = .white 52 | 53 | view.addSubview(nameLabel) 54 | view.addSubview(idLabel) 55 | view.addSubview(actionButton) 56 | 57 | nameLabel.translatesAutoresizingMaskIntoConstraints = false 58 | idLabel.translatesAutoresizingMaskIntoConstraints = false 59 | 60 | NSLayoutConstraint.activate([ 61 | nameLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20), 62 | nameLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), 63 | nameLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), 64 | 65 | idLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 10), 66 | idLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), 67 | idLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), 68 | 69 | actionButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20), 70 | actionButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), 71 | actionButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), 72 | actionButton.heightAnchor.constraint(equalToConstant: 50) 73 | ]) 74 | } 75 | 76 | private func onCompletion() { 77 | fetchUser() 78 | viewModel?.onError = { [weak self] error in 79 | self?.handle(error: error) 80 | } 81 | viewModel?.userList = { [weak self] item in 82 | guard let item = item?.data else {return} 83 | self?.updateUI(with: item[0]) 84 | } 85 | 86 | } 87 | 88 | func fetchUser() { 89 | fetchUserCalled = true 90 | viewModel?.getUser() 91 | } 92 | 93 | func updateUI(with user: UserListDatum) { 94 | nameLabel.text = (user.firstName ?? "") + " " + (user.lastName ?? "") 95 | idLabel.text = "ID: \(user.id ?? 0)" 96 | } 97 | 98 | func handle(error: Error) { 99 | let alert = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert) 100 | alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) 101 | presentedAlert = alert 102 | present(alert, animated: true, completion: nil) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /AssertlyInUse.xcodeproj/xcshareddata/xcschemes/AssertlyInUse.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 35 | 41 | 42 | 43 | 46 | 52 | 53 | 54 | 55 | 56 | 66 | 68 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /Assertly/AssertlyMain/ViewModelBaseTest/AssertlyViewModelTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewModelTests.swift 3 | // AssertlyTests 4 | // 5 | // Created by Sharon Omoyeni Babatunde on 06/07/2024. 6 | // 7 | 8 | import XCTest 9 | @testable import Assertly 10 | 11 | public class AssertlyViewModelTests: XCTestCase { 12 | 13 | public var sut: T? 14 | 15 | public override func setUp() { 16 | super.setUp() 17 | sut = createDependencies() 18 | } 19 | 20 | public override func tearDown() { 21 | sut = nil 22 | super.tearDown() 23 | } 24 | 25 | public func createDependencies() -> T { 26 | fatalError("Override \("createDependencies") this method in subclass") 27 | } 28 | 29 | public func testAction(_ action: String, expectedResult: R) { 30 | let expectation = self.expectation(description: "Test \(action)") 31 | 32 | (sut as? MockViewModel)?.performAction(action) { (result: Result) in 33 | switch result { 34 | case .success(let value): 35 | XCTAssertEqual(value, expectedResult) 36 | case .failure: 37 | XCTFail("Expected success, got failure") 38 | } 39 | expectation.fulfill() 40 | } 41 | 42 | waitForExpectations(timeout: 1, handler: nil) 43 | } 44 | 45 | public func testArrayAction(_ action: String, expectedResult: [Element]) { 46 | let expectation = self.expectation(description: "Test Array \(action)") 47 | 48 | (sut as? MockViewModel)?.performArrayAction(action) { (result: Result<[Element], Error>) in 49 | switch result { 50 | case .success(let value): 51 | XCTAssertEqual(value, expectedResult) 52 | case .failure: 53 | XCTFail("Expected success, got failure") 54 | } 55 | expectation.fulfill() 56 | } 57 | 58 | waitForExpectations(timeout: 1, handler: nil) 59 | } 60 | 61 | public func testDictionaryAction(_ action: String, expectedResult: [Key: Value]) { 62 | let expectation = self.expectation(description: "Test Dictionary \(action)") 63 | 64 | (sut as? MockViewModel)?.performDictionaryAction(action) { (result: Result<[Key: Value], Error>) in 65 | switch result { 66 | case .success(let value): 67 | XCTAssertEqual(value, expectedResult) 68 | case .failure: 69 | XCTFail("Expected success, got failure") 70 | } 71 | expectation.fulfill() 72 | } 73 | 74 | waitForExpectations(timeout: 1, handler: nil) 75 | } 76 | 77 | public func testActionError(_ action: String, expectedError: Error, as type: R.Type) { 78 | let expectation = self.expectation(description: "Test \(action) Error") 79 | 80 | (sut as? MockViewModel)?.performAction(action) { (result: Result) in 81 | switch result { 82 | case .success: 83 | XCTFail("Expected failure, got success") 84 | case .failure(let error): 85 | XCTAssertEqual(error.localizedDescription, expectedError.localizedDescription) 86 | } 87 | expectation.fulfill() 88 | } 89 | 90 | waitForExpectations(timeout: 1, handler: nil) 91 | } 92 | 93 | public func assertElementProperties( 94 | in viewController: UIViewController, 95 | elements: [String: (getter: () -> R?, expectedValue: R)] 96 | ) { 97 | for (elementName, (getter, expectedValue)) in elements { 98 | guard let actualValue = getter() else { 99 | XCTFail("Element \(elementName) not found in view controller or its value is nil") 100 | continue 101 | } 102 | 103 | XCTAssertEqual(actualValue, expectedValue, "Property for \(elementName) does not match expected value") 104 | } 105 | } 106 | 107 | public func assertElementProperties( 108 | in viewController: UIViewController, 109 | elements: [String: (keyPath: KeyPath, expectedValue: V)] 110 | ) { 111 | for (elementName, (keyPath, expectedValue)) in elements { 112 | guard let element = viewController.value(forKey: elementName) as? R else { 113 | XCTFail("Element \(elementName) not found in view controller or is not of expected type") 114 | continue 115 | } 116 | 117 | let actualValue = element[keyPath: keyPath] 118 | XCTAssertEqual(actualValue, expectedValue, "Property for \(elementName) (\(T.self)) does not match expected value") 119 | } 120 | } 121 | 122 | public func assertBoolean(_ getter: @escaping () -> Bool, expectedValue: Bool, message: String? = nil) { 123 | let actualValue = getter() 124 | if expectedValue { 125 | XCTAssertTrue(actualValue, message ?? "Expected true, but got false") 126 | } else { 127 | XCTAssertFalse(actualValue, message ?? "Expected false, but got true") 128 | } 129 | } 130 | 131 | public func assertNotNil(_ expression: @autoclosure () -> V?, 132 | _ message: @autoclosure () -> String = "", 133 | file: StaticString = #file, 134 | line: UInt = #line) { 135 | let value = expression() 136 | XCTAssertNotNil(value, message(), file: file, line: line) 137 | } 138 | 139 | @discardableResult 140 | public func assertNotNilAndUnwrap(_ expression: @autoclosure () -> V?, 141 | _ message: @autoclosure () -> String = "", 142 | file: StaticString = #file, 143 | line: UInt = #line) -> V? { 144 | let value = expression() 145 | XCTAssertNotNil(value, message(), file: file, line: line) 146 | return value 147 | } 148 | 149 | public func assertEqual(_ expression1: @autoclosure () -> V, 150 | _ expression2: @autoclosure () -> V, 151 | _ message: @autoclosure () -> String = "", 152 | file: StaticString = #file, 153 | line: UInt = #line) { 154 | let value1 = expression1() 155 | let value2 = expression2() 156 | XCTAssertEqual(value1, value2, message(), file: file, line: line) 157 | } 158 | 159 | public func assertEqualOptional(_ expression1: @autoclosure () -> V?, 160 | _ expression2: @autoclosure () -> V?, 161 | _ message: @autoclosure () -> String = "", 162 | file: StaticString = #file, 163 | line: UInt = #line) { 164 | let value1 = expression1() 165 | let value2 = expression2() 166 | XCTAssertEqual(value1, value2, message(), file: file, line: line) 167 | } 168 | 169 | public func assertTrue(_ expression: @autoclosure () -> Bool, 170 | _ message: @autoclosure () -> String = "", 171 | file: StaticString = #file, 172 | line: UInt = #line) { 173 | let value = expression() 174 | XCTAssertTrue(value, message(), file: file, line: line) 175 | } 176 | 177 | public func assertFalse(_ expression: @autoclosure () -> Bool, 178 | _ message: @autoclosure () -> String = "", 179 | file: StaticString = #file, 180 | line: UInt = #line) { 181 | let value = expression() 182 | XCTAssertFalse(value, message(), file: file, line: line) 183 | } 184 | 185 | public func assertNotEqual(_ expression1: @autoclosure () -> V, 186 | _ expression2: @autoclosure () -> V, 187 | _ message: @autoclosure () -> String = "", 188 | file: StaticString = #file, 189 | line: UInt = #line) { 190 | let value1 = expression1() 191 | let value2 = expression2() 192 | XCTAssertNotEqual(value1, value2, message(), file: file, line: line) 193 | } 194 | 195 | public func assertNotIdentical(_ expression1: @autoclosure () -> AnyObject?, 196 | _ expression2: @autoclosure () -> AnyObject?, 197 | _ message: @autoclosure () -> String = "", 198 | file: StaticString = #file, 199 | line: UInt = #line) { 200 | let value1 = expression1() 201 | let value2 = expression2() 202 | XCTAssertNotIdentical(value1, value2, message(), file: file, line: line) 203 | } 204 | 205 | public func assertNoThrow(_ expression: @autoclosure () throws -> V, 206 | _ message: @autoclosure () -> String = "", 207 | file: StaticString = #file, 208 | line: UInt = #line) { 209 | XCTAssertNoThrow(try expression(), message(), file: file, line: line) 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Assertly:** Simplifying Swift Unit Testing 2 | Assertly is an open-source Swift framework designed to streamline and enhance unit testing in iOS applications. It provides a set of powerful, easy-to-use assertion methods and testing utilities that simplify the process of writing and maintaining unit tests for controllers, view models, and network classes. 3 | Key Benefits 4 | 5 | Simplified Syntax: Assertly offers a more intuitive and readable syntax for common assertions, reducing boilerplate code and making tests easier to write and understand. 6 | Improved Error Messages: Custom assertions provide more detailed and context-specific error messages, making it easier to identify and fix issues. 7 | Support for Asynchronous Testing: Built-in utilities for testing asynchronous operations, reducing the complexity of testing network calls and other async processes. 8 | Type-Safe Assertions: Leverages Swift's type system to catch potential errors at compile-time, reducing runtime errors. 9 | Consistency: Promotes a consistent testing style across your project, improving maintainability and readability. 10 | 11 | ****Installation**** 12 | 13 | **Swift Package Manager (SPM)** 14 | To install AssertlySwift package via Xcode 15 | 16 | Go to File -> Swift Packages -> Add Package Dependency... 17 | Then search for https://github.com/yeniObabatunde/Assertly.git 18 | And Add the package to **your unit Test folder** only 19 | 20 | **Core Features 21 | Custom Assertions** 22 | Assertly provides a suite of custom assertion methods that extend XCTest's functionality: 23 | 24 | ```assertEqual``` and ```assertEqualOptional```: For comparing equatable values. 25 | ```assertTrue``` and ```assertFalse```: For boolean checks. 26 | ```assertNotEqual```: For ensuring values are different. 27 | ```assertNotIdentical```: For checking object instances. 28 | ```assertNoThrow```: For verifying that code doesn't throw errors. 29 | ```assertNotNil``` and ```assertNotNilAndUnwrap```: For optional value checks. 30 | 31 | ```AssertlyViewModelTests``` **ViewModelTests Base Class** 32 | A powerful base class for testing view models, providing: 33 | 34 | Automatic setup and teardown of dependencies. 35 | Methods for testing actions, array actions, and dictionary actions. 36 | Utilities for testing error scenarios. 37 | 38 | **Asynchronous Testing Support** 39 | Built-in support for testing asynchronous operations, including: 40 | 41 | **Expectation management.** 42 | Convenient methods for delayed assertions. 43 | 44 | **Usage Examples 45 | Testing a View Controller** 46 | 47 | ``` 48 | class YourViewControllerClassTests: AssertlyViewModelTests { 49 | var viewController: MyViewController! 50 | 51 | override func createDependencies() -> MockViewModel { 52 | return MockViewModel() 53 | } 54 | 55 | override func setUp() { 56 | super.setUp() 57 | viewController = MockSampleViewController() 58 | viewController.loadViewIfNeeded() 59 | } 60 | 61 | func testUpdateUI_WithValidUser() { 62 | //MARK: Given 63 | let expectation = self.expectation(description: "Fetch User Success") 64 | let expectedUser = UserListDatum(id: 1, email: "john@example.com", firstName: "John", lastName: "Doe", avatar: "avatar_url") 65 | sut?.setMockResult(expectedUser, for: "getUser") 66 | 67 | //MARK: When 68 | viewController.fetchUser() 69 | 70 | //MARK: Then 71 | self.assertTrue(self.viewController.fetchUserCalled) 72 | assertNotEqual(expectedUser.email, "johnexample@gmail.com") 73 | expectation.fulfill() 74 | 75 | waitForExpectations(timeout: 1, handler: nil) 76 | } 77 | ``` 78 | 79 | **Testing a ViewModel** 80 | 81 | ``` 82 | class YourViewModelTestsClass: AssertlyViewModelTests { 83 | func testFetchUser() { 84 | let mockUser = User(id: 1, name: "John Doe") 85 | sut?.setMockResult(mockUser, for: "fetchUser") 86 | 87 | testAction("fetchUser", expectedResult: mockUser) 88 | } 89 | 90 | func testFetchUsers() { 91 | let mockUsers = [User(id: 1, name: "John"), User(id: 2, name: "Jane")] 92 | (sut)?.setMockArrayResult(mockUsers, for: "fetchUsers") 93 | 94 | testArrayAction("fetchUsers", expectedResult: mockUsers) 95 | } 96 | let mockUserDict = [1: User(id: 1, name: "John"), 2: User(id: 2, name: "Jane")] 97 | (sut)?.setMockDictionaryResult(mockUserDict, for: "fetchUserDict") 98 | 99 | testDictionaryAction("fetchUserDict", expectedResult: mockUserDict) 100 | } 101 | 102 | func testFetchUserError() { 103 | let mockError = NSError(domain: "UserError", code: 404, userInfo: [NSLocalizedDescriptionKey: "User not found"]) 104 | (sut)?.setMockError(mockError, for: "fetchUser") 105 | 106 | testActionError("fetchUser", expectedError: mockError, as: NSError.self) 107 | } 108 | 109 | func testFetchUserFailure() { 110 | //MARK: This should fail because expected result doesn't equal actual result 111 | let expectedUser = User(id: 1, name: "John Doe") 112 | let actualUser = User(id: 2, name: "Jane Smith") // Different user 113 | sut?.setMockResult(actualUser, for: "fetchUser") 114 | assertNotEqual(actualUser, expectedUser) 115 | } 116 | } 117 | 118 | ``` 119 | 120 | Testing Network Calls 121 | ``` 122 | class YourNetworkServiceTests: BaseTestCase, UnitTestable{ 123 | var networking: MockNetworking! 124 | 125 | required init(dependencies: MockNetworking) { 126 | super.init() 127 | self.networking = dependencies 128 | } 129 | 130 | override func setUp() { 131 | super.setUp() 132 | } 133 | 134 | override func tearDown() { 135 | super.tearDown() 136 | } 137 | 138 | override func createDependencies() -> MockNetworking { 139 | return MockNetworking() 140 | } 141 | 142 | func testSuccessfulRequest() { 143 | let expectation = self.expectation(description: "Successful request") 144 | let mockData = ["key": "value"] 145 | networking.mockResult = .success(mockData) 146 | 147 | networking.request("testEndpoint") { (result: Result<[String: String], Error>) in 148 | switch result { 149 | case .success(let data): 150 | XCTAssertEqual(data, mockData) 151 | case .failure: 152 | XCTFail("Expected success, got failure") 153 | } 154 | expectation.fulfill() 155 | } 156 | 157 | waitForExpectations(timeout: 1, handler: nil) 158 | } 159 | } 160 | ``` 161 | **Why Assertly?** 162 | Assertly was developed to address common pain points in iOS unit testing: 163 | 164 | **Verbosity:** Standard XCTest assertions often require verbose syntax. Assertly's custom assertions are more concise and expressive. 165 | Asynchronous Testing Complexity: Testing async code with XCTest can be cumbersome. Assertly provides utilities to simplify this process. 166 | Inconsistent Testing Patterns: Teams often develop inconsistent testing styles. Assertly encourages a unified approach to writing tests. 167 | Limited Error Information: XCTest's default error messages can be vague. Assertly's assertions provide more context when tests fail. 168 | Repetitive Setup Code: The ViewModelTests base class reduces the amount of boilerplate needed for testing view models. 169 | 170 | **Implementation** 171 | A sample project demonstrating the implementation of Assertly is included in the repository. This project showcases: 172 | 173 | **How to integrate Assertly into your Xcode project.** 174 | Examples of testing various components (ViewControllers, ViewModels, NetworkServices) using Assertly. 175 | Best practices for structuring your tests with Assertly. 176 | 177 | **Conclusion** 178 | Assertly aims to make unit testing in Swift more accessible, efficient, and enjoyable. By providing a robust set of tools and promoting best practices, it helps developers write more reliable and maintainable tests, ultimately leading to higher quality iOS applications. 179 | We encourage contributions and feedback from the community to continue improving and expanding Assertly's capabilities. 180 | 181 | **Future Development and Contributions** 182 | We are actively working on expanding Assertly's capabilities and are particularly focused on developing support for UI testing. Our goal is to bring the same level of simplification and efficiency to UI testing that we've achieved with unit testing. 183 | We warmly welcome contributions from the community in various areas: 184 | 185 | **UI Testing Support:** Help us develop and refine tools for streamlining UI tests. 186 | **Additional Assertion Methods: **Propose and implement new assertion methods that could benefit the iOS testing community. 187 | **Performance Improvements:** Optimize existing code for better performance. 188 | **Documentation and Examples:** Enhance our documentation or provide additional usage examples. 189 | **Integration with Other Tools:** Develop integrations with popular iOS development and testing tools. 190 | B**ug Fixes and Refinements:** Help identify and fix any issues in the existing codebase. 191 | 192 | Whether you're an experienced developer or just starting out, your input is valuable. Feel free to submit pull requests, open issues for discussion, or share your ideas on how we can make Assertly even better. 193 | 194 | **How to Contribute 195 | ** 196 | We appreciate your interest in contributing to Assertly! Here's how you can get started: 197 | 198 | Fork the repository 199 | Clone your fork to your local machine 200 | Create a new branch from the feature/Contribute branch, following this naming convention: 201 | ``` 202 | git checkout -b feature/Contribute/"NewChanges" 203 | ``` 204 | Replace "NewChanges" with a brief description of your contribution. For example: 205 | ``` 206 | git checkout -b feature/Contribute/UITestSupport **OR** git checkout -b feature/Contribute/NewAssertionMethod 207 | ``` 208 | Commit your changes: 209 | ``` 210 | git commit -m 'Add some AmazingFeature' 211 | ``` 212 | Push to the branch: 213 | ``` 214 | git push origin feature/Contribute/"NewChanges" 215 | ``` 216 | Open a Pull Request from your new branch to our feature/Contribute branch: 217 | 218 | Please ensure your code adheres to our coding standards and includes appropriate tests and documentation for new features. 219 | We review all pull requests and appreciate your patience during this process. We may suggest some changes or improvements. 220 | Thank you for helping make Assertly better for everyone in the iOS development community! 221 | -------------------------------------------------------------------------------- /AssertlyInUse.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 70; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 581DB4E42D63FE5700CF30A0 /* NetworkHandling in Frameworks */ = {isa = PBXBuildFile; productRef = 581DB4E32D63FE5700CF30A0 /* NetworkHandling */; }; 11 | 581DB4E72D63FEA000CF30A0 /* Assertly in Frameworks */ = {isa = PBXBuildFile; productRef = 581DB4E62D63FEA000CF30A0 /* Assertly */; }; 12 | 5861BB8C2C38CCC2001382DB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5861BB8B2C38CCC2001382DB /* AppDelegate.swift */; }; 13 | 5861BB8E2C38CCC2001382DB /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5861BB8D2C38CCC2001382DB /* SceneDelegate.swift */; }; 14 | 5861BB952C38CCC5001382DB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5861BB942C38CCC5001382DB /* Assets.xcassets */; }; 15 | 5861BB982C38CCC5001382DB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5861BB962C38CCC5001382DB /* LaunchScreen.storyboard */; }; 16 | 5861BBA32C38CCC5001382DB /* AssertlyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5861BBA22C38CCC5001382DB /* AssertlyTests.swift */; }; 17 | 5861BBAD2C38CCC5001382DB /* AssertlyUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5861BBAC2C38CCC5001382DB /* AssertlyUITests.swift */; }; 18 | 5861BBAF2C38CCC5001382DB /* AssertlyUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5861BBAE2C38CCC5001382DB /* AssertlyUITestsLaunchTests.swift */; }; 19 | 5861BBD52C38D6EC001382DB /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5861BBD42C38D6EC001382DB /* NetworkError.swift */; }; 20 | 5861BBDE2C38DE24001382DB /* Extension+Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5861BBDD2C38DE24001382DB /* Extension+Array.swift */; }; 21 | 588AF5EE2C39A82F0068FE04 /* MockSampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 588AF5ED2C39A82F0068FE04 /* MockSampleViewController.swift */; }; 22 | 588AF5F32C39ACC80068FE04 /* Users.swift in Sources */ = {isa = PBXBuildFile; fileRef = 588AF5F22C39ACC80068FE04 /* Users.swift */; }; 23 | 588AF5F92C39F8120068FE04 /* SampleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 588AF5F82C39F8120068FE04 /* SampleViewModel.swift */; }; 24 | 58AF973C2C554354004E1628 /* AppConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AF973B2C554354004E1628 /* AppConstants.swift */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXContainerItemProxy section */ 28 | 5861BB9F2C38CCC5001382DB /* PBXContainerItemProxy */ = { 29 | isa = PBXContainerItemProxy; 30 | containerPortal = 5861BB802C38CCC2001382DB /* Project object */; 31 | proxyType = 1; 32 | remoteGlobalIDString = 5861BB872C38CCC2001382DB; 33 | remoteInfo = Assertly; 34 | }; 35 | 5861BBA92C38CCC5001382DB /* PBXContainerItemProxy */ = { 36 | isa = PBXContainerItemProxy; 37 | containerPortal = 5861BB802C38CCC2001382DB /* Project object */; 38 | proxyType = 1; 39 | remoteGlobalIDString = 5861BB872C38CCC2001382DB; 40 | remoteInfo = Assertly; 41 | }; 42 | /* End PBXContainerItemProxy section */ 43 | 44 | /* Begin PBXFileReference section */ 45 | 5861BB882C38CCC2001382DB /* AssertlyInUse.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AssertlyInUse.app; sourceTree = BUILT_PRODUCTS_DIR; }; 46 | 5861BB8B2C38CCC2001382DB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 47 | 5861BB8D2C38CCC2001382DB /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 48 | 5861BB942C38CCC5001382DB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 49 | 5861BB972C38CCC5001382DB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 50 | 5861BB992C38CCC5001382DB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 51 | 5861BB9E2C38CCC5001382DB /* AssertlyInUseTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AssertlyInUseTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | 5861BBA22C38CCC5001382DB /* AssertlyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssertlyTests.swift; sourceTree = ""; }; 53 | 5861BBA82C38CCC5001382DB /* AssertlyInUseUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AssertlyInUseUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | 5861BBAC2C38CCC5001382DB /* AssertlyUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssertlyUITests.swift; sourceTree = ""; }; 55 | 5861BBAE2C38CCC5001382DB /* AssertlyUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssertlyUITestsLaunchTests.swift; sourceTree = ""; }; 56 | 5861BBD42C38D6EC001382DB /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = ""; }; 57 | 5861BBDD2C38DE24001382DB /* Extension+Array.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension+Array.swift"; sourceTree = ""; }; 58 | 588AF5ED2C39A82F0068FE04 /* MockSampleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSampleViewController.swift; sourceTree = ""; }; 59 | 588AF5F22C39ACC80068FE04 /* Users.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Users.swift; sourceTree = ""; }; 60 | 588AF5F82C39F8120068FE04 /* SampleViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleViewModel.swift; sourceTree = ""; }; 61 | 58AF973B2C554354004E1628 /* AppConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConstants.swift; sourceTree = ""; }; 62 | /* End PBXFileReference section */ 63 | 64 | /* Begin PBXFileSystemSynchronizedRootGroup section */ 65 | 584238E82D63F31300F3EDDD /* SampleNetworkingTest */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = SampleNetworkingTest; sourceTree = ""; }; 66 | 584238E92D63F32B00F3EDDD /* ErrorHandlingTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = ErrorHandlingTests; sourceTree = ""; }; 67 | 584238EA2D63F35200F3EDDD /* SampleControllerTest */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = SampleControllerTest; sourceTree = ""; }; 68 | 584238EB2D63F37700F3EDDD /* SampleUseViewModelTest */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = SampleUseViewModelTest; sourceTree = ""; }; 69 | /* End PBXFileSystemSynchronizedRootGroup section */ 70 | 71 | /* Begin PBXFrameworksBuildPhase section */ 72 | 5861BB852C38CCC2001382DB /* Frameworks */ = { 73 | isa = PBXFrameworksBuildPhase; 74 | buildActionMask = 2147483647; 75 | files = ( 76 | 581DB4E42D63FE5700CF30A0 /* NetworkHandling in Frameworks */, 77 | ); 78 | runOnlyForDeploymentPostprocessing = 0; 79 | }; 80 | 5861BB9B2C38CCC5001382DB /* Frameworks */ = { 81 | isa = PBXFrameworksBuildPhase; 82 | buildActionMask = 2147483647; 83 | files = ( 84 | 581DB4E72D63FEA000CF30A0 /* Assertly in Frameworks */, 85 | ); 86 | runOnlyForDeploymentPostprocessing = 0; 87 | }; 88 | 5861BBA52C38CCC5001382DB /* Frameworks */ = { 89 | isa = PBXFrameworksBuildPhase; 90 | buildActionMask = 2147483647; 91 | files = ( 92 | ); 93 | runOnlyForDeploymentPostprocessing = 0; 94 | }; 95 | /* End PBXFrameworksBuildPhase section */ 96 | 97 | /* Begin PBXGroup section */ 98 | 5861BB7F2C38CCC2001382DB = { 99 | isa = PBXGroup; 100 | children = ( 101 | 5861BB8A2C38CCC2001382DB /* Assertly */, 102 | 5861BBA12C38CCC5001382DB /* AssertlyTests */, 103 | 5861BBAB2C38CCC5001382DB /* AssertlyUITests */, 104 | 5861BB892C38CCC2001382DB /* Products */, 105 | ); 106 | sourceTree = ""; 107 | }; 108 | 5861BB892C38CCC2001382DB /* Products */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 5861BB882C38CCC2001382DB /* AssertlyInUse.app */, 112 | 5861BB9E2C38CCC5001382DB /* AssertlyInUseTests.xctest */, 113 | 5861BBA82C38CCC5001382DB /* AssertlyInUseUITests.xctest */, 114 | ); 115 | name = Products; 116 | sourceTree = ""; 117 | }; 118 | 5861BB8A2C38CCC2001382DB /* Assertly */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 5861BBC52C38D15F001382DB /* App */, 122 | 5861BBDF2C38E3F7001382DB /* SampleApp */, 123 | 5861BBBB2C38CEC9001382DB /* Utils */, 124 | 5861BBC62C38D16C001382DB /* Resources */, 125 | 5861BB992C38CCC5001382DB /* Info.plist */, 126 | ); 127 | path = Assertly; 128 | sourceTree = ""; 129 | }; 130 | 5861BBA12C38CCC5001382DB /* AssertlyTests */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | 584238EB2D63F37700F3EDDD /* SampleUseViewModelTest */, 134 | 584238EA2D63F35200F3EDDD /* SampleControllerTest */, 135 | 584238E92D63F32B00F3EDDD /* ErrorHandlingTests */, 136 | 584238E82D63F31300F3EDDD /* SampleNetworkingTest */, 137 | 5861BBA22C38CCC5001382DB /* AssertlyTests.swift */, 138 | ); 139 | path = AssertlyTests; 140 | sourceTree = ""; 141 | }; 142 | 5861BBAB2C38CCC5001382DB /* AssertlyUITests */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | 5861BBAC2C38CCC5001382DB /* AssertlyUITests.swift */, 146 | 5861BBAE2C38CCC5001382DB /* AssertlyUITestsLaunchTests.swift */, 147 | ); 148 | path = AssertlyUITests; 149 | sourceTree = ""; 150 | }; 151 | 5861BBBB2C38CEC9001382DB /* Utils */ = { 152 | isa = PBXGroup; 153 | children = ( 154 | 5861BBD42C38D6EC001382DB /* NetworkError.swift */, 155 | 5861BBDD2C38DE24001382DB /* Extension+Array.swift */, 156 | 58AF973B2C554354004E1628 /* AppConstants.swift */, 157 | ); 158 | path = Utils; 159 | sourceTree = ""; 160 | }; 161 | 5861BBC52C38D15F001382DB /* App */ = { 162 | isa = PBXGroup; 163 | children = ( 164 | 5861BB8D2C38CCC2001382DB /* SceneDelegate.swift */, 165 | 5861BB8B2C38CCC2001382DB /* AppDelegate.swift */, 166 | ); 167 | path = App; 168 | sourceTree = ""; 169 | }; 170 | 5861BBC62C38D16C001382DB /* Resources */ = { 171 | isa = PBXGroup; 172 | children = ( 173 | 5861BB962C38CCC5001382DB /* LaunchScreen.storyboard */, 174 | 5861BB942C38CCC5001382DB /* Assets.xcassets */, 175 | ); 176 | path = Resources; 177 | sourceTree = ""; 178 | }; 179 | 5861BBDF2C38E3F7001382DB /* SampleApp */ = { 180 | isa = PBXGroup; 181 | children = ( 182 | 588AF5F12C39ACBC0068FE04 /* Model */, 183 | 588AF5FF2C3A25C60068FE04 /* ViewController */, 184 | 588AF5F72C39F7FC0068FE04 /* ViewModel */, 185 | ); 186 | path = SampleApp; 187 | sourceTree = ""; 188 | }; 189 | 588AF5F12C39ACBC0068FE04 /* Model */ = { 190 | isa = PBXGroup; 191 | children = ( 192 | 588AF5F22C39ACC80068FE04 /* Users.swift */, 193 | ); 194 | path = Model; 195 | sourceTree = ""; 196 | }; 197 | 588AF5F72C39F7FC0068FE04 /* ViewModel */ = { 198 | isa = PBXGroup; 199 | children = ( 200 | 588AF5F82C39F8120068FE04 /* SampleViewModel.swift */, 201 | ); 202 | path = ViewModel; 203 | sourceTree = ""; 204 | }; 205 | 588AF5FF2C3A25C60068FE04 /* ViewController */ = { 206 | isa = PBXGroup; 207 | children = ( 208 | 588AF5ED2C39A82F0068FE04 /* MockSampleViewController.swift */, 209 | ); 210 | path = ViewController; 211 | sourceTree = ""; 212 | }; 213 | /* End PBXGroup section */ 214 | 215 | /* Begin PBXNativeTarget section */ 216 | 5861BB872C38CCC2001382DB /* AssertlyInUse */ = { 217 | isa = PBXNativeTarget; 218 | buildConfigurationList = 5861BBB22C38CCC5001382DB /* Build configuration list for PBXNativeTarget "AssertlyInUse" */; 219 | buildPhases = ( 220 | 5861BB842C38CCC2001382DB /* Sources */, 221 | 5861BB852C38CCC2001382DB /* Frameworks */, 222 | 5861BB862C38CCC2001382DB /* Resources */, 223 | ); 224 | buildRules = ( 225 | ); 226 | dependencies = ( 227 | ); 228 | name = AssertlyInUse; 229 | packageProductDependencies = ( 230 | 581DB4E32D63FE5700CF30A0 /* NetworkHandling */, 231 | ); 232 | productName = Assertly; 233 | productReference = 5861BB882C38CCC2001382DB /* AssertlyInUse.app */; 234 | productType = "com.apple.product-type.application"; 235 | }; 236 | 5861BB9D2C38CCC5001382DB /* AssertlyInUseTests */ = { 237 | isa = PBXNativeTarget; 238 | buildConfigurationList = 5861BBB52C38CCC5001382DB /* Build configuration list for PBXNativeTarget "AssertlyInUseTests" */; 239 | buildPhases = ( 240 | 5861BB9A2C38CCC5001382DB /* Sources */, 241 | 5861BB9B2C38CCC5001382DB /* Frameworks */, 242 | 5861BB9C2C38CCC5001382DB /* Resources */, 243 | ); 244 | buildRules = ( 245 | ); 246 | dependencies = ( 247 | 5861BBA02C38CCC5001382DB /* PBXTargetDependency */, 248 | ); 249 | fileSystemSynchronizedGroups = ( 250 | 584238E82D63F31300F3EDDD /* SampleNetworkingTest */, 251 | 584238E92D63F32B00F3EDDD /* ErrorHandlingTests */, 252 | 584238EA2D63F35200F3EDDD /* SampleControllerTest */, 253 | 584238EB2D63F37700F3EDDD /* SampleUseViewModelTest */, 254 | ); 255 | name = AssertlyInUseTests; 256 | productName = AssertlyTests; 257 | productReference = 5861BB9E2C38CCC5001382DB /* AssertlyInUseTests.xctest */; 258 | productType = "com.apple.product-type.bundle.unit-test"; 259 | }; 260 | 5861BBA72C38CCC5001382DB /* AssertlyInUseUITests */ = { 261 | isa = PBXNativeTarget; 262 | buildConfigurationList = 5861BBB82C38CCC5001382DB /* Build configuration list for PBXNativeTarget "AssertlyInUseUITests" */; 263 | buildPhases = ( 264 | 5861BBA42C38CCC5001382DB /* Sources */, 265 | 5861BBA52C38CCC5001382DB /* Frameworks */, 266 | 5861BBA62C38CCC5001382DB /* Resources */, 267 | ); 268 | buildRules = ( 269 | ); 270 | dependencies = ( 271 | 5861BBAA2C38CCC5001382DB /* PBXTargetDependency */, 272 | ); 273 | name = AssertlyInUseUITests; 274 | productName = AssertlyUITests; 275 | productReference = 5861BBA82C38CCC5001382DB /* AssertlyInUseUITests.xctest */; 276 | productType = "com.apple.product-type.bundle.ui-testing"; 277 | }; 278 | /* End PBXNativeTarget section */ 279 | 280 | /* Begin PBXProject section */ 281 | 5861BB802C38CCC2001382DB /* Project object */ = { 282 | isa = PBXProject; 283 | attributes = { 284 | BuildIndependentTargetsInParallel = 1; 285 | LastSwiftUpdateCheck = 1500; 286 | LastUpgradeCheck = 1500; 287 | TargetAttributes = { 288 | 5861BB872C38CCC2001382DB = { 289 | CreatedOnToolsVersion = 15.0.1; 290 | }; 291 | 5861BB9D2C38CCC5001382DB = { 292 | CreatedOnToolsVersion = 15.0.1; 293 | TestTargetID = 5861BB872C38CCC2001382DB; 294 | }; 295 | 5861BBA72C38CCC5001382DB = { 296 | CreatedOnToolsVersion = 15.0.1; 297 | TestTargetID = 5861BB872C38CCC2001382DB; 298 | }; 299 | }; 300 | }; 301 | buildConfigurationList = 5861BB832C38CCC2001382DB /* Build configuration list for PBXProject "AssertlyInUse" */; 302 | compatibilityVersion = "Xcode 14.0"; 303 | developmentRegion = en; 304 | hasScannedForEncodings = 0; 305 | knownRegions = ( 306 | en, 307 | Base, 308 | ); 309 | mainGroup = 5861BB7F2C38CCC2001382DB; 310 | packageReferences = ( 311 | 581DB4E22D63FE5700CF30A0 /* XCRemoteSwiftPackageReference "NetworkHandlerPackage" */, 312 | 581DB4E52D63FEA000CF30A0 /* XCRemoteSwiftPackageReference "Assertly" */, 313 | ); 314 | productRefGroup = 5861BB892C38CCC2001382DB /* Products */; 315 | projectDirPath = ""; 316 | projectRoot = ""; 317 | targets = ( 318 | 5861BB872C38CCC2001382DB /* AssertlyInUse */, 319 | 5861BB9D2C38CCC5001382DB /* AssertlyInUseTests */, 320 | 5861BBA72C38CCC5001382DB /* AssertlyInUseUITests */, 321 | ); 322 | }; 323 | /* End PBXProject section */ 324 | 325 | /* Begin PBXResourcesBuildPhase section */ 326 | 5861BB862C38CCC2001382DB /* Resources */ = { 327 | isa = PBXResourcesBuildPhase; 328 | buildActionMask = 2147483647; 329 | files = ( 330 | 5861BB982C38CCC5001382DB /* LaunchScreen.storyboard in Resources */, 331 | 5861BB952C38CCC5001382DB /* Assets.xcassets in Resources */, 332 | ); 333 | runOnlyForDeploymentPostprocessing = 0; 334 | }; 335 | 5861BB9C2C38CCC5001382DB /* Resources */ = { 336 | isa = PBXResourcesBuildPhase; 337 | buildActionMask = 2147483647; 338 | files = ( 339 | ); 340 | runOnlyForDeploymentPostprocessing = 0; 341 | }; 342 | 5861BBA62C38CCC5001382DB /* Resources */ = { 343 | isa = PBXResourcesBuildPhase; 344 | buildActionMask = 2147483647; 345 | files = ( 346 | ); 347 | runOnlyForDeploymentPostprocessing = 0; 348 | }; 349 | /* End PBXResourcesBuildPhase section */ 350 | 351 | /* Begin PBXSourcesBuildPhase section */ 352 | 5861BB842C38CCC2001382DB /* Sources */ = { 353 | isa = PBXSourcesBuildPhase; 354 | buildActionMask = 2147483647; 355 | files = ( 356 | 588AF5EE2C39A82F0068FE04 /* MockSampleViewController.swift in Sources */, 357 | 588AF5F92C39F8120068FE04 /* SampleViewModel.swift in Sources */, 358 | 588AF5F32C39ACC80068FE04 /* Users.swift in Sources */, 359 | 5861BBDE2C38DE24001382DB /* Extension+Array.swift in Sources */, 360 | 5861BBD52C38D6EC001382DB /* NetworkError.swift in Sources */, 361 | 58AF973C2C554354004E1628 /* AppConstants.swift in Sources */, 362 | 5861BB8C2C38CCC2001382DB /* AppDelegate.swift in Sources */, 363 | 5861BB8E2C38CCC2001382DB /* SceneDelegate.swift in Sources */, 364 | ); 365 | runOnlyForDeploymentPostprocessing = 0; 366 | }; 367 | 5861BB9A2C38CCC5001382DB /* Sources */ = { 368 | isa = PBXSourcesBuildPhase; 369 | buildActionMask = 2147483647; 370 | files = ( 371 | 5861BBA32C38CCC5001382DB /* AssertlyTests.swift in Sources */, 372 | ); 373 | runOnlyForDeploymentPostprocessing = 0; 374 | }; 375 | 5861BBA42C38CCC5001382DB /* Sources */ = { 376 | isa = PBXSourcesBuildPhase; 377 | buildActionMask = 2147483647; 378 | files = ( 379 | 5861BBAF2C38CCC5001382DB /* AssertlyUITestsLaunchTests.swift in Sources */, 380 | 5861BBAD2C38CCC5001382DB /* AssertlyUITests.swift in Sources */, 381 | ); 382 | runOnlyForDeploymentPostprocessing = 0; 383 | }; 384 | /* End PBXSourcesBuildPhase section */ 385 | 386 | /* Begin PBXTargetDependency section */ 387 | 5861BBA02C38CCC5001382DB /* PBXTargetDependency */ = { 388 | isa = PBXTargetDependency; 389 | target = 5861BB872C38CCC2001382DB /* AssertlyInUse */; 390 | targetProxy = 5861BB9F2C38CCC5001382DB /* PBXContainerItemProxy */; 391 | }; 392 | 5861BBAA2C38CCC5001382DB /* PBXTargetDependency */ = { 393 | isa = PBXTargetDependency; 394 | target = 5861BB872C38CCC2001382DB /* AssertlyInUse */; 395 | targetProxy = 5861BBA92C38CCC5001382DB /* PBXContainerItemProxy */; 396 | }; 397 | /* End PBXTargetDependency section */ 398 | 399 | /* Begin PBXVariantGroup section */ 400 | 5861BB962C38CCC5001382DB /* LaunchScreen.storyboard */ = { 401 | isa = PBXVariantGroup; 402 | children = ( 403 | 5861BB972C38CCC5001382DB /* Base */, 404 | ); 405 | name = LaunchScreen.storyboard; 406 | sourceTree = ""; 407 | }; 408 | /* End PBXVariantGroup section */ 409 | 410 | /* Begin XCBuildConfiguration section */ 411 | 5861BBB02C38CCC5001382DB /* Debug */ = { 412 | isa = XCBuildConfiguration; 413 | buildSettings = { 414 | ALWAYS_SEARCH_USER_PATHS = NO; 415 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 416 | CLANG_ANALYZER_NONNULL = YES; 417 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 418 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 419 | CLANG_ENABLE_MODULES = YES; 420 | CLANG_ENABLE_OBJC_ARC = YES; 421 | CLANG_ENABLE_OBJC_WEAK = YES; 422 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 423 | CLANG_WARN_BOOL_CONVERSION = YES; 424 | CLANG_WARN_COMMA = YES; 425 | CLANG_WARN_CONSTANT_CONVERSION = YES; 426 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 427 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 428 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 429 | CLANG_WARN_EMPTY_BODY = YES; 430 | CLANG_WARN_ENUM_CONVERSION = YES; 431 | CLANG_WARN_INFINITE_RECURSION = YES; 432 | CLANG_WARN_INT_CONVERSION = YES; 433 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 434 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 435 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 436 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 437 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 438 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 439 | CLANG_WARN_STRICT_PROTOTYPES = YES; 440 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 441 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 442 | CLANG_WARN_UNREACHABLE_CODE = YES; 443 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 444 | COPY_PHASE_STRIP = NO; 445 | DEBUG_INFORMATION_FORMAT = dwarf; 446 | ENABLE_STRICT_OBJC_MSGSEND = YES; 447 | ENABLE_TESTABILITY = YES; 448 | ENABLE_TESTING_SEARCH_PATHS = YES; 449 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 450 | GCC_C_LANGUAGE_STANDARD = gnu17; 451 | GCC_DYNAMIC_NO_PIC = NO; 452 | GCC_NO_COMMON_BLOCKS = YES; 453 | GCC_OPTIMIZATION_LEVEL = 0; 454 | GCC_PREPROCESSOR_DEFINITIONS = ( 455 | "DEBUG=1", 456 | "$(inherited)", 457 | ); 458 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 459 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 460 | GCC_WARN_UNDECLARED_SELECTOR = YES; 461 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 462 | GCC_WARN_UNUSED_FUNCTION = YES; 463 | GCC_WARN_UNUSED_VARIABLE = YES; 464 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 465 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 466 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 467 | MTL_FAST_MATH = YES; 468 | ONLY_ACTIVE_ARCH = NO; 469 | SDKROOT = iphoneos; 470 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 471 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 472 | }; 473 | name = Debug; 474 | }; 475 | 5861BBB12C38CCC5001382DB /* Release */ = { 476 | isa = XCBuildConfiguration; 477 | buildSettings = { 478 | ALWAYS_SEARCH_USER_PATHS = NO; 479 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 480 | CLANG_ANALYZER_NONNULL = YES; 481 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 482 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 483 | CLANG_ENABLE_MODULES = YES; 484 | CLANG_ENABLE_OBJC_ARC = YES; 485 | CLANG_ENABLE_OBJC_WEAK = YES; 486 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 487 | CLANG_WARN_BOOL_CONVERSION = YES; 488 | CLANG_WARN_COMMA = YES; 489 | CLANG_WARN_CONSTANT_CONVERSION = YES; 490 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 491 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 492 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 493 | CLANG_WARN_EMPTY_BODY = YES; 494 | CLANG_WARN_ENUM_CONVERSION = YES; 495 | CLANG_WARN_INFINITE_RECURSION = YES; 496 | CLANG_WARN_INT_CONVERSION = YES; 497 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 498 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 499 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 500 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 501 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 502 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 503 | CLANG_WARN_STRICT_PROTOTYPES = YES; 504 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 505 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 506 | CLANG_WARN_UNREACHABLE_CODE = YES; 507 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 508 | COPY_PHASE_STRIP = NO; 509 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 510 | ENABLE_NS_ASSERTIONS = NO; 511 | ENABLE_STRICT_OBJC_MSGSEND = YES; 512 | ENABLE_TESTING_SEARCH_PATHS = YES; 513 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 514 | GCC_C_LANGUAGE_STANDARD = gnu17; 515 | GCC_NO_COMMON_BLOCKS = YES; 516 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 517 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 518 | GCC_WARN_UNDECLARED_SELECTOR = YES; 519 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 520 | GCC_WARN_UNUSED_FUNCTION = YES; 521 | GCC_WARN_UNUSED_VARIABLE = YES; 522 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 523 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 524 | MTL_ENABLE_DEBUG_INFO = NO; 525 | MTL_FAST_MATH = YES; 526 | SDKROOT = iphoneos; 527 | SWIFT_COMPILATION_MODE = wholemodule; 528 | VALIDATE_PRODUCT = YES; 529 | }; 530 | name = Release; 531 | }; 532 | 5861BBB32C38CCC5001382DB /* Debug */ = { 533 | isa = XCBuildConfiguration; 534 | buildSettings = { 535 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 536 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 537 | BASE_URL = "https://reqres.in/api/users"; 538 | CODE_SIGN_STYLE = Automatic; 539 | CURRENT_PROJECT_VERSION = 1; 540 | DEVELOPMENT_TEAM = 8JM7VY488D; 541 | GENERATE_INFOPLIST_FILE = YES; 542 | INFOPLIST_FILE = Assertly/Info.plist; 543 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 544 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 545 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 546 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 547 | LD_RUNPATH_SEARCH_PATHS = ( 548 | "$(inherited)", 549 | "@executable_path/Frameworks", 550 | ); 551 | MARKETING_VERSION = 1.0; 552 | ONLY_ACTIVE_ARCH = NO; 553 | PRODUCT_BUNDLE_IDENTIFIER = Items.Assertly; 554 | PRODUCT_NAME = "$(TARGET_NAME)"; 555 | SWIFT_EMIT_LOC_STRINGS = YES; 556 | SWIFT_VERSION = 5.0; 557 | TARGETED_DEVICE_FAMILY = "1,2"; 558 | }; 559 | name = Debug; 560 | }; 561 | 5861BBB42C38CCC5001382DB /* Release */ = { 562 | isa = XCBuildConfiguration; 563 | buildSettings = { 564 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 565 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 566 | BASE_URL = "https://reqres.in/api/users"; 567 | CODE_SIGN_STYLE = Automatic; 568 | CURRENT_PROJECT_VERSION = 1; 569 | DEVELOPMENT_TEAM = 8JM7VY488D; 570 | GENERATE_INFOPLIST_FILE = YES; 571 | INFOPLIST_FILE = Assertly/Info.plist; 572 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 573 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 574 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 575 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 576 | LD_RUNPATH_SEARCH_PATHS = ( 577 | "$(inherited)", 578 | "@executable_path/Frameworks", 579 | ); 580 | MARKETING_VERSION = 1.0; 581 | PRODUCT_BUNDLE_IDENTIFIER = Items.Assertly; 582 | PRODUCT_NAME = "$(TARGET_NAME)"; 583 | SWIFT_EMIT_LOC_STRINGS = YES; 584 | SWIFT_VERSION = 5.0; 585 | TARGETED_DEVICE_FAMILY = "1,2"; 586 | }; 587 | name = Release; 588 | }; 589 | 5861BBB62C38CCC5001382DB /* Debug */ = { 590 | isa = XCBuildConfiguration; 591 | buildSettings = { 592 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 593 | BUNDLE_LOADER = "$(TEST_HOST)"; 594 | CODE_SIGN_STYLE = Automatic; 595 | CURRENT_PROJECT_VERSION = 1; 596 | DEVELOPMENT_TEAM = 8JM7VY488D; 597 | GENERATE_INFOPLIST_FILE = YES; 598 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 599 | MARKETING_VERSION = 1.0; 600 | PRODUCT_BUNDLE_IDENTIFIER = Items.AssertlyTests; 601 | PRODUCT_NAME = "$(TARGET_NAME)"; 602 | SWIFT_EMIT_LOC_STRINGS = NO; 603 | SWIFT_VERSION = 5.0; 604 | TARGETED_DEVICE_FAMILY = "1,2"; 605 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AssertlyInUse.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/AssertlyInUse"; 606 | }; 607 | name = Debug; 608 | }; 609 | 5861BBB72C38CCC5001382DB /* Release */ = { 610 | isa = XCBuildConfiguration; 611 | buildSettings = { 612 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 613 | BUNDLE_LOADER = "$(TEST_HOST)"; 614 | CODE_SIGN_STYLE = Automatic; 615 | CURRENT_PROJECT_VERSION = 1; 616 | DEVELOPMENT_TEAM = 8JM7VY488D; 617 | GENERATE_INFOPLIST_FILE = YES; 618 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 619 | MARKETING_VERSION = 1.0; 620 | PRODUCT_BUNDLE_IDENTIFIER = Items.AssertlyTests; 621 | PRODUCT_NAME = "$(TARGET_NAME)"; 622 | SWIFT_EMIT_LOC_STRINGS = NO; 623 | SWIFT_VERSION = 5.0; 624 | TARGETED_DEVICE_FAMILY = "1,2"; 625 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AssertlyInUse.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/AssertlyInUse"; 626 | }; 627 | name = Release; 628 | }; 629 | 5861BBB92C38CCC5001382DB /* Debug */ = { 630 | isa = XCBuildConfiguration; 631 | buildSettings = { 632 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 633 | CODE_SIGN_STYLE = Automatic; 634 | CURRENT_PROJECT_VERSION = 1; 635 | DEVELOPMENT_TEAM = 8JM7VY488D; 636 | GENERATE_INFOPLIST_FILE = YES; 637 | MARKETING_VERSION = 1.0; 638 | PRODUCT_BUNDLE_IDENTIFIER = Items.AssertlyUITests; 639 | PRODUCT_NAME = "$(TARGET_NAME)"; 640 | SWIFT_EMIT_LOC_STRINGS = NO; 641 | SWIFT_VERSION = 5.0; 642 | TARGETED_DEVICE_FAMILY = "1,2"; 643 | TEST_TARGET_NAME = Assertly; 644 | }; 645 | name = Debug; 646 | }; 647 | 5861BBBA2C38CCC5001382DB /* Release */ = { 648 | isa = XCBuildConfiguration; 649 | buildSettings = { 650 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 651 | CODE_SIGN_STYLE = Automatic; 652 | CURRENT_PROJECT_VERSION = 1; 653 | DEVELOPMENT_TEAM = 8JM7VY488D; 654 | GENERATE_INFOPLIST_FILE = YES; 655 | MARKETING_VERSION = 1.0; 656 | PRODUCT_BUNDLE_IDENTIFIER = Items.AssertlyUITests; 657 | PRODUCT_NAME = "$(TARGET_NAME)"; 658 | SWIFT_EMIT_LOC_STRINGS = NO; 659 | SWIFT_VERSION = 5.0; 660 | TARGETED_DEVICE_FAMILY = "1,2"; 661 | TEST_TARGET_NAME = Assertly; 662 | }; 663 | name = Release; 664 | }; 665 | /* End XCBuildConfiguration section */ 666 | 667 | /* Begin XCConfigurationList section */ 668 | 5861BB832C38CCC2001382DB /* Build configuration list for PBXProject "AssertlyInUse" */ = { 669 | isa = XCConfigurationList; 670 | buildConfigurations = ( 671 | 5861BBB02C38CCC5001382DB /* Debug */, 672 | 5861BBB12C38CCC5001382DB /* Release */, 673 | ); 674 | defaultConfigurationIsVisible = 0; 675 | defaultConfigurationName = Release; 676 | }; 677 | 5861BBB22C38CCC5001382DB /* Build configuration list for PBXNativeTarget "AssertlyInUse" */ = { 678 | isa = XCConfigurationList; 679 | buildConfigurations = ( 680 | 5861BBB32C38CCC5001382DB /* Debug */, 681 | 5861BBB42C38CCC5001382DB /* Release */, 682 | ); 683 | defaultConfigurationIsVisible = 0; 684 | defaultConfigurationName = Release; 685 | }; 686 | 5861BBB52C38CCC5001382DB /* Build configuration list for PBXNativeTarget "AssertlyInUseTests" */ = { 687 | isa = XCConfigurationList; 688 | buildConfigurations = ( 689 | 5861BBB62C38CCC5001382DB /* Debug */, 690 | 5861BBB72C38CCC5001382DB /* Release */, 691 | ); 692 | defaultConfigurationIsVisible = 0; 693 | defaultConfigurationName = Release; 694 | }; 695 | 5861BBB82C38CCC5001382DB /* Build configuration list for PBXNativeTarget "AssertlyInUseUITests" */ = { 696 | isa = XCConfigurationList; 697 | buildConfigurations = ( 698 | 5861BBB92C38CCC5001382DB /* Debug */, 699 | 5861BBBA2C38CCC5001382DB /* Release */, 700 | ); 701 | defaultConfigurationIsVisible = 0; 702 | defaultConfigurationName = Release; 703 | }; 704 | /* End XCConfigurationList section */ 705 | 706 | /* Begin XCRemoteSwiftPackageReference section */ 707 | 581DB4E22D63FE5700CF30A0 /* XCRemoteSwiftPackageReference "NetworkHandlerPackage" */ = { 708 | isa = XCRemoteSwiftPackageReference; 709 | repositoryURL = "https://github.com/yeniObabatunde/NetworkHandlerPackage.git"; 710 | requirement = { 711 | branch = main; 712 | kind = branch; 713 | }; 714 | }; 715 | 581DB4E52D63FEA000CF30A0 /* XCRemoteSwiftPackageReference "Assertly" */ = { 716 | isa = XCRemoteSwiftPackageReference; 717 | repositoryURL = "https://github.com/yeniObabatunde/Assertly.git"; 718 | requirement = { 719 | branch = main; 720 | kind = branch; 721 | }; 722 | }; 723 | /* End XCRemoteSwiftPackageReference section */ 724 | 725 | /* Begin XCSwiftPackageProductDependency section */ 726 | 581DB4E32D63FE5700CF30A0 /* NetworkHandling */ = { 727 | isa = XCSwiftPackageProductDependency; 728 | package = 581DB4E22D63FE5700CF30A0 /* XCRemoteSwiftPackageReference "NetworkHandlerPackage" */; 729 | productName = NetworkHandling; 730 | }; 731 | 581DB4E62D63FEA000CF30A0 /* Assertly */ = { 732 | isa = XCSwiftPackageProductDependency; 733 | package = 581DB4E52D63FEA000CF30A0 /* XCRemoteSwiftPackageReference "Assertly" */; 734 | productName = Assertly; 735 | }; 736 | /* End XCSwiftPackageProductDependency section */ 737 | }; 738 | rootObject = 5861BB802C38CCC2001382DB /* Project object */; 739 | } 740 | --------------------------------------------------------------------------------