ios/AuthenticatorBridgeKit/Tests/TestHelpers/Support/AuthenticatorBridgeKitTestCase.swift

199 lines
8.1 KiB
Swift

import XCTest
/// Base class for any tests in the AuthenticatorBridgeKit framework.
///
open class AuthenticatorBridgeKitTestCase: XCTestCase {
/// Asserts that an asynchronous block of code will throw an error. The test will fail if the
/// block does not throw an error.
///
/// - Note: This method does not rethrow the error thrown by `block`.
///
/// - Parameters:
/// - block: The block to be executed. This block is run asynchronously.
/// - file: The file in which the failure occurred. Defaults to the file name of the test
/// case in which the function was called from.
/// - line: The line number in which the failure occurred. Defaults to the line number on
/// which this function was called from.
///
open func assertAsyncThrows(
_ block: () async throws -> Void,
file: StaticString = #file,
line: UInt = #line
) async {
do {
try await block()
XCTFail("The block did not throw an error.", file: file, line: line)
} catch {}
}
/// Asserts that an asynchronous block of code will throw a specific error. The test will fail
/// if the block does not throw an error or if the error thrown does not equal the provided error.
///
/// - Note: This method does not rethrow the error thrown by `block`.
///
/// - Parameters:
/// - error: The specific error that must be thrown by `block`.
/// - block: The block to be executed. This block is run asynchronously.
/// - file: The file in which the failure occurred. Defaults to the file name of the test
/// case in which the function was called from.
/// - line: The line number in which the failure occurred. Defaults to the line number on
/// which this function was called from.
///
open func assertAsyncThrows<E: Error & Equatable>(
error: E,
file: StaticString = #file,
line: UInt = #line,
_ block: () async throws -> Void
) async {
do {
try await block()
XCTFail("The block did not throw an error.", file: file, line: line)
} catch let caughtError as E {
XCTAssertEqual(caughtError, error, file: file, line: line)
} catch let caughtError {
XCTFail(
"The error caught (\(caughtError)) does not match the type of error provided (\(error)).",
file: file,
line: line
)
}
}
/// Asserts that an asynchronous block of code does not throw an error. The test will fail
/// if the block throws an error.
///
/// - Parameters:
/// - block: The block to be executed. This block is run asynchronously.
/// - file: The file in which the failure occurred. Defaults to the file name of the test
/// case in which the function was called from.
/// - line: The line number in which the failure occurred. Defaults to the line number on
/// which this function was called from.
///
open func assertAsyncDoesNotThrow(
_ block: () async throws -> Void,
file: StaticString = #file,
line: UInt = #line
) async {
do {
try await block()
} catch {
XCTFail("The block threw an error.", file: file, line: line)
}
}
/// Wait for a condition to be true. The test will fail if the condition isn't met before the
/// specified timeout.
///
/// - Parameters:
/// - condition: Return `true` to continue or `false` to keep waiting.
/// - timeout: How long to wait before failing.
/// - failureMessage: Message to display when the condition fails to be met.
/// - file: The file in which the failure occurred. Defaults to the file name of the test
/// case in which the function was called from.
/// - line: The line number in which the failure occurred. Defaults to the line number on
/// which this function was called from.
///
open func waitFor(
_ condition: () -> Bool,
timeout: TimeInterval = 10.0,
failureMessage: String = "waitFor condition wasn't met within the time limit",
file: StaticString = #file,
line: UInt = #line
) {
let start = Date()
let limit = Date(timeIntervalSinceNow: timeout)
while !condition(), limit > Date() {
let next = Date(timeIntervalSinceNow: 0.2)
RunLoop.current.run(mode: RunLoop.Mode.default, before: next)
}
warnIfNeeded(start: start, line: line)
XCTAssert(condition(), failureMessage, file: file, line: line)
}
/// Wait for a condition to be true. The test will fail if the condition isn't met before the
/// specified timeout.
///
/// - Parameters:
/// - condition: An expression that evaluates to `true` to continue or `false` to keep waiting.
/// - timeout: How long to wait before failing.
/// - failureMessage: Message to display when the condition fails to be met.
/// - file: The file in which the failure occurred. Defaults to the file name of the test
/// case in which the function was called from.
/// - line: The line number in which the failure occurred. Defaults to the line number on
/// which this function was called from.
///
open func waitFor(
_ condition: @autoclosure () -> Bool,
timeout: TimeInterval = 10.0,
failureMessage: String = "waitFor condition wasn't met within the time limit",
file: StaticString = #file,
line: UInt = #line
) {
waitFor(
condition,
timeout: timeout,
failureMessage: failureMessage,
file: file,
line: line
)
}
/// Wait for a condition asynchronously to be true. The test will fail if the condition isn't met before the
/// specified timeout.
///
/// - Parameters:
/// - condition: Return `true` to continue or `false` to keep waiting.
/// - timeout: How long to wait before failing.
/// - failureMessage: Message to display when the condition fails to be met.
/// - file: The file in which the failure occurred. Defaults to the file name of the test
/// case in which the function was called from.
/// - line: The line number in which the failure occurred. Defaults to the line number on
/// which this function was called from.
///
open func waitForAsync(
_ condition: @escaping () -> Bool,
timeout: TimeInterval = 10.0,
failureMessage: String = "waitForAsync condition wasn't met within the time limit",
file: StaticString = #file,
line: UInt = #line
) async throws {
let start = Date()
let limit = Date(timeIntervalSinceNow: timeout)
while !condition(), limit > Date() {
try await Task.sleep(nanoseconds: 2 * 100_000_000)
}
warnIfNeeded(start: start, line: line)
XCTAssert(condition(), failureMessage, file: file, line: line)
}
/// Warns if `functionName` took more than `afterSeconds` to complete
/// - Parameters:
/// - start: When `waitFor` started
/// - afterSeconds: The seconds that have passed since `start` to check against
/// - functionName: The function name
/// - line: File line were this was originated
private func warnIfNeeded(
start: Date,
afterSeconds: Int = 3,
functionName: String = #function,
line: UInt = #line
) {
// If the condition took more than 3 seconds to satisfy, add a warning to the logs to look into it.
let elapsed = Date().timeIntervalSince(start)
if elapsed > 3 {
let numberFormatter = NumberFormatter()
numberFormatter.maximumFractionDigits = 3
numberFormatter.minimumFractionDigits = 3
numberFormatter.minimumIntegerDigits = 1
let elapsedString: String = numberFormatter.string(from: NSNumber(value: elapsed)) ?? "nil"
print("warning: \(name) line \(line) `\(functionName)` took \(elapsedString) seconds")
}
}
}