-
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
7beab71
commit a867b12
Showing
10 changed files
with
365 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,48 +2,33 @@ name: OTPKitTests | |
|
||
on: | ||
push: | ||
branches: [ main ] | ||
branches: [main] | ||
pull_request: | ||
branches: [ main ] | ||
branches: [main] | ||
|
||
jobs: | ||
build: | ||
runs-on: macos-latest | ||
|
||
steps: | ||
- uses: actions/checkout@v2 | ||
|
||
- name: Switch Xcode 15 | ||
run: sudo xcode-select -switch /Applications/Xcode_15.0.1.app | ||
|
||
- name: Install xcodegen | ||
run: brew install xcodegen | ||
|
||
- name: Generate xcodeproj for OTPKit | ||
run: xcodegen | ||
|
||
# Build | ||
- name: Build OneBusAway | ||
run: xcodebuild clean build-for-testing | ||
-scheme 'OTPKitDemo' | ||
-destination 'platform=iOS Simulator,name=iPhone 15' | ||
-quiet | ||
|
||
# Unit Test | ||
- name: OBAKit Unit Test | ||
run: xcodebuild test-without-building | ||
-only-testing:OTPKitTests | ||
-project 'OTPKit.xcodeproj' | ||
-scheme 'OTPKitDemo' | ||
-destination 'platform=iOS Simulator,name=iPhone 15' | ||
-resultBundlePath OTPKitTests.xcresult | ||
-quiet | ||
|
||
# Upload results | ||
- uses: kishikawakatsumi/[email protected] | ||
continue-on-error: true | ||
with: | ||
show-passed-tests: false # Avoid truncation of annotations by GitHub by omitting succeeding tests. | ||
path: | | ||
OTPKitTests.xcresult | ||
if: success() || failure() | ||
- uses: actions/checkout@v2 | ||
|
||
- name: Switch Xcode 15 | ||
run: sudo xcode-select -switch /Applications/Xcode_15.0.1.app | ||
|
||
# Build | ||
- name: Build OTPKit | ||
run: | | ||
xcodebuild build-for-testing \ | ||
-scheme OTPKit \ | ||
-destination "platform=iOS Simulator,name=iPhone 15,OS=latest" \ | ||
-enableCodeCoverage YES | ||
# Upload results | ||
- uses: kishikawakatsumi/[email protected] | ||
continue-on-error: true | ||
with: | ||
show-passed-tests: false # Avoid truncation of annotations by GitHub by omitting succeeding tests. | ||
path: | | ||
OTPKitTests.xcresult | ||
if: success() || failure() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
/* | ||
* Copyright (C) Open Transit Software Foundation | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
import Foundation | ||
@testable import OTPKit | ||
|
||
class Fixtures { | ||
private class var testBundle: Bundle { | ||
Bundle.module | ||
} | ||
|
||
/// Converts the specified dictionary to a model object of type `T`. | ||
/// - Parameters: | ||
/// - type: The model type to which the dictionary will be converted. | ||
/// - dictionary: The data | ||
/// - Returns: A model object | ||
class func dictionaryToModel<T>(type: T.Type, dictionary: [String: Any]) throws -> T where T: Decodable { | ||
let jsonData = try JSONSerialization.data(withJSONObject: dictionary, options: []) | ||
return try JSONDecoder().decode(type, from: jsonData) | ||
} | ||
|
||
/// Returns the path to the specified file in the test bundle. | ||
/// - Parameter fileName: The file name, e.g. "regions.json" | ||
class func path(to fileName: String) -> String { | ||
testBundle.path(forResource: fileName, ofType: nil)! | ||
} | ||
|
||
/// Encodes and decodes the provided `Codable` object. Useful for testing roundtripping. | ||
/// - Parameter type: The object type. | ||
/// - Parameter model: The object or objects. | ||
class func roundtripCodable<T>(type: T.Type, model: T) throws -> T where T: Codable { | ||
let encoded = try PropertyListEncoder().encode(model) | ||
let decoded = try PropertyListDecoder().decode(type, from: encoded) | ||
return decoded | ||
} | ||
|
||
/// Loads data from the specified file name, searching within the test bundle. | ||
/// - Parameter file: The file name to load data from. Example: `stop_data.pb`. | ||
class func loadData(file: String) -> Data { | ||
NSData(contentsOfFile: path(to: file))! as Data | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
/* | ||
* Copyright (C) Open Transit Software Foundation | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
import Foundation | ||
import OTPKit | ||
|
||
typealias MockDataLoaderMatcher = (URLRequest) -> Bool | ||
|
||
struct MockDataResponse { | ||
let data: Data? | ||
let urlResponse: URLResponse? | ||
let error: Error? | ||
let matcher: MockDataLoaderMatcher | ||
} | ||
|
||
class MockTask: URLSessionDataTask { | ||
override var progress: Progress { | ||
Progress() | ||
} | ||
|
||
private var closure: (Data?, URLResponse?, Error?) -> Void | ||
private let mockResponse: MockDataResponse | ||
|
||
init(mockResponse: MockDataResponse, closure: @escaping (Data?, URLResponse?, Error?) -> Void) { | ||
self.mockResponse = mockResponse | ||
self.closure = closure | ||
} | ||
|
||
// We override the 'resume' method and simply call our closure | ||
// instead of actually resuming any task. | ||
override func resume() { | ||
closure(mockResponse.data, mockResponse.urlResponse, mockResponse.error) | ||
} | ||
|
||
override func cancel() { | ||
// nop | ||
} | ||
} | ||
|
||
class MockDataLoader: NSObject, URLDataLoader { | ||
var mockResponses = [MockDataResponse]() | ||
|
||
let testName: String | ||
|
||
init(testName: String) { | ||
self.testName = testName | ||
} | ||
|
||
func dataTask( | ||
with request: URLRequest, | ||
completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void | ||
) -> URLSessionDataTask { | ||
guard let response = matchResponse(to: request) else { | ||
fatalError("\(testName): Missing response to URL: \(request.url!)") | ||
} | ||
|
||
return MockTask(mockResponse: response, closure: completionHandler) | ||
} | ||
|
||
func data(for request: URLRequest) async throws -> (Data, URLResponse) { | ||
guard let response = matchResponse(to: request) else { | ||
fatalError("\(testName): Missing response to URL: \(request.url!)") | ||
} | ||
|
||
if let error = response.error { | ||
throw error | ||
} | ||
|
||
guard let data = response.data else { | ||
fatalError("\(testName): Missing data to URL: \(request.url!))") | ||
} | ||
|
||
guard let urlResponse = response.urlResponse else { | ||
fatalError("\(testName): Missing urlResponse to URL: \(request.url!))") | ||
} | ||
|
||
return (data, urlResponse) | ||
} | ||
|
||
// MARK: - Response Mapping | ||
|
||
func matchResponse(to request: URLRequest) -> MockDataResponse? { | ||
for r in mockResponses where r.matcher(request) { | ||
return r | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func mock(data: Data, matcher: @escaping MockDataLoaderMatcher) { | ||
let urlResponse = buildURLResponse(URL: URL(string: "https://mockdataloader.example.com")!, statusCode: 200) | ||
let mockResponse = MockDataResponse(data: data, urlResponse: urlResponse, error: nil, matcher: matcher) | ||
mock(response: mockResponse) | ||
} | ||
|
||
func mock(URLString: String, with data: Data) { | ||
mock(url: URL(string: URLString)!, with: data) | ||
} | ||
|
||
func mock(url: URL, with data: Data) { | ||
let urlResponse = buildURLResponse(URL: url, statusCode: 200) | ||
let mockResponse = MockDataResponse(data: data, urlResponse: urlResponse, error: nil) { | ||
let requestURL = $0.url! | ||
return requestURL.host == url.host && requestURL.path == url.path | ||
} | ||
mock(response: mockResponse) | ||
} | ||
|
||
func mock(response: MockDataResponse) { | ||
mockResponses.append(response) | ||
} | ||
|
||
func removeMappedResponses() { | ||
mockResponses.removeAll() | ||
} | ||
|
||
// MARK: - URL Response | ||
|
||
func buildURLResponse(URL: URL, statusCode: Int) -> HTTPURLResponse { | ||
HTTPURLResponse( | ||
url: URL, | ||
statusCode: statusCode, | ||
httpVersion: "2", | ||
headerFields: ["Content-Type": "application/json"] | ||
)! | ||
} | ||
|
||
// MARK: - Description | ||
|
||
override var debugDescription: String { | ||
var descriptionBuilder = DebugDescriptionBuilder(baseDescription: super.debugDescription) | ||
descriptionBuilder.add(key: "mockResponses", value: mockResponses) | ||
return descriptionBuilder.description | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
// | ||
// OTPTestCase.swift | ||
// OTPKitTests | ||
// | ||
// Created by Aaron Brethorst on 5/2/24. | ||
// | ||
|
||
import Foundation | ||
@testable import OTPKit | ||
import XCTest | ||
|
||
public class OTPTestCase: XCTestCase { | ||
var userDefaults: UserDefaults! | ||
|
||
override open func setUp() { | ||
super.setUp() | ||
NSTimeZone.default = NSTimeZone(forSecondsFromGMT: 0) as TimeZone | ||
userDefaults = buildUserDefaults() | ||
userDefaults.removePersistentDomain(forName: userDefaultsSuiteName) | ||
} | ||
|
||
override open func tearDown() { | ||
super.tearDown() | ||
NSTimeZone.resetSystemTimeZone() | ||
userDefaults.removePersistentDomain(forName: userDefaultsSuiteName) | ||
} | ||
|
||
// MARK: - User Defaults | ||
|
||
func buildUserDefaults(suiteName: String? = nil) -> UserDefaults { | ||
UserDefaults(suiteName: suiteName ?? userDefaultsSuiteName)! | ||
} | ||
|
||
var userDefaultsSuiteName: String { | ||
String(describing: self) | ||
} | ||
|
||
// MARK: - Network and Data | ||
|
||
func buildMockDataLoader() -> MockDataLoader { | ||
MockDataLoader(testName: name) | ||
} | ||
|
||
func buildRestAPIClient( | ||
baseURLString: String = "https://otp.prod.sound.obaweb.org/otp/routers/default/" | ||
) -> RestAPI { | ||
let baseURL = URL(string: baseURLString)! | ||
return RestAPI(baseURL: baseURL, dataLoader: buildMockDataLoader()) | ||
} | ||
} |
Oops, something went wrong.