Skip to content

Commit

Permalink
Add XCTAssertThrowsErrorAsync (#22)
Browse files Browse the repository at this point in the history
# Add XCTAssertThrowsErrorAsync

## ♻️ Current situation & Problem
This PR adds a async version of the
[`XCTAssertThrowsError(_:_:file:line:_:)`](https://developer.apple.com/documentation/xctest/1500795-xctassertthrowserror)
method.


This method was originally introduced in SpeziBluetooth but was now
found to be useful as well in SpeziDevices. Therefore, there is a need
to have this available as a reusable implementation.


## ⚙️ Release Notes 
* Add `XCTAssertThrowsErrorAsync(_:_:file:line:_:)` method.


## 📚 Documentation
New interfaces were documented. Additionally the documentation catalog
was restructured to provide a bit more overview of supported types.


## ✅ Testing
Minimal testing.


### Code of Conduct & Contributing Guidelines 

By submitting creating this pull request, you agree to follow our [Code
of
Conduct](https://github.com/StanfordBDHG/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/StanfordBDHG/.github/blob/main/CONTRIBUTING.md):
- [x] I agree to follow the [Code of
Conduct](https://github.com/StanfordBDHG/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/StanfordBDHG/.github/blob/main/CONTRIBUTING.md).
  • Loading branch information
Supereg authored Jun 27, 2024
1 parent 1fe9b8e commit 69eae1a
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 22 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,5 @@ jobs:
uses: StanfordBDHG/.github/.github/workflows/create-and-upload-coverage-report.yml@v2
with:
coveragereports: XCTestExtensions-iOS.xcresult XCTestExtensions-watchOS.xcresult XCTestExtensions-visionOS.xcresult XCTestExtensions-macOS.xcresult TestApp-iOS.xcresult TestApp-visionOS.xcresult
secrets:
token: ${{ secrets.CODECOV_TOKEN }}
19 changes: 19 additions & 0 deletions .github/workflows/monthly-markdown-link-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#
# This source file is part of the Stanford Spezi open source project
#
# SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md)
#
# SPDX-License-Identifier: MIT
#

name: Monthly Markdown Link Check

on:
# Runs at midnight on the first of every month
schedule:
- cron: "0 0 1 * *"

jobs:
markdown_link_check:
name: Markdown Link Check
uses: StanfordBDHG/.github/.github/workflows/markdown-link-check.yml@v2
3 changes: 3 additions & 0 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ authors:
- family-names: "Schmiedmayer"
given-names: "Paul"
orcid: "https://orcid.org/0000-0002-8607-9148"
- family-names: "Bauer"
given-names: "Andreas"
orcid: "https://orcid.org/0000-0002-1680-237X"
title: "XCTestExtensions"
doi: 10.5281/zenodo.7606320
url: "https://github.com/StanfordBDHG/XCTestExtensions"
20 changes: 18 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@
import PackageDescription


#if swift(<6)
let swiftConcurrency: SwiftSetting = .enableExperimentalFeature("SwiftConcurrency")
#else
let swiftConcurrency: SwiftSetting = .enableUpcomingFeature("SwiftConcurrency")
#endif


let package = Package(
name: "XCTestExtensions",
platforms: [
Expand All @@ -25,15 +32,24 @@ let package = Package(
],
targets: [
.target(
name: "XCTestApp"
name: "XCTestApp",
swiftSettings: [
swiftConcurrency
]
),
.target(
name: "XCTestExtensions"
name: "XCTestExtensions",
swiftSettings: [
swiftConcurrency
]
),
.testTarget(
name: "XCTestExtensionsTests",
dependencies: [
.target(name: "XCTestExtensions")
],
swiftSettings: [
swiftConcurrency
]
)
]
Expand Down
35 changes: 35 additions & 0 deletions Sources/XCTestExtensions/XCTAssertThrowsErrorAsync.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// This source file is part of the Stanford XCTestExtensions open-source project
//
// SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import XCTest


/// Asserts that an async expression throws an error.
///
/// - Note: This is an async version of [`XCTAssertThrowsError(_:_:file:line:_:)`](https://developer.apple.com/documentation/xctest/1500795-xctassertthrowserror).
///
/// - Parameters:
/// - expression: An async expression that can throw an error.
/// - message: An optional description of a failure.
/// - file: The file where the failure occurs. The default is the filename of the test case where you call this function.
/// - line: The line number where the failure occurs. The default is the line number where you call this function.
/// - errorHandler: An optional handler for errors that expression throws.
public func XCTAssertThrowsErrorAsync<T>(
_ expression: @autoclosure () async throws -> T,
_ message: @autoclosure () -> String = "",
file: StaticString = #filePath,
line: UInt = #line,
_ errorHandler: (Error) throws -> Void = { _ in }
) async rethrows {
do {
_ = try await expression()
XCTFail(message(), file: file, line: line)
} catch {
try errorHandler(error)
}
}
19 changes: 15 additions & 4 deletions Sources/XCTestExtensions/XCTestCase+DisablePasswordAutofill.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,16 @@ extension XCTestCase {
/// Use this function used to to disable password autofill by navigating to the iOS settings app and turning off the password autofill functionality in the settings UI.
///
/// > Warning: While this workaround worked well until 17.2, we experienced a crash of the passwords section in the IOS 17.2 passwords app on the iOS simulator, which no longer allows us to use this workaround.
/// We recommend using a custom setup script to skip password-related functionality in your UI tests until there is a better workaround. Plase inspect the logic to setup simulators in the [xcodebuild-or-fastlane.yml](https://github.com/StanfordBDHG/.github/blob/main/.github/workflows/xcodebuild-or-fastlane.yml) workflow and be sure to `setupSimulators: true` if you use the GitHub action as a reusable workflow.
@available(iOS, deprecated: 17.2, message: "Please use a custom setup script in your CI environment to disable password autofill.")
/// We recommend using a custom setup script to skip password-related functionality in your UI tests until there is a better workaround. Please inspect the logic to setup simulators in the [xcodebuild-or-fastlane.yml](https://github.com/StanfordBDHG/.github/blob/main/.github/workflows/xcodebuild-or-fastlane.yml) workflow and be sure to `setupSimulators: true` if you use the GitHub action as a reusable workflow.
@available(
iOS,
deprecated: 17.2,
message: """
To avoid having the password autofill interfere with your UI test, \
avoid specifying the password text content type for simulator builds.
This method will be removed in a future version.
"""
)
@available(watchOS, unavailable)
@available(macOS, unavailable)
@available(tvOS, unavailable)
Expand All @@ -45,7 +53,9 @@ extension XCTestCase {
}

let springboard = XCUIApplication(bundleIdentifier: XCUIApplication.homeScreenBundle)
if springboard.secureTextFields["Passcode field"].waitForExistence(timeout: 30.0) {

sleep(1)
if springboard.secureTextFields["Passcode field"].exists {
let passcodeInput = springboard.secureTextFields["Passcode field"]
passcodeInput.tap()

Expand All @@ -54,7 +64,8 @@ extension XCTestCase {
passcodeInput.typeText("1234\r")

sleep(2)
} else {
} else if #unavailable(iOS 17.4) {
// other versions just don't need a passcode anymore
os_log("Could not enter the passcode in the device to enter the password section in the settings app.")
throw XCTestError(.failureWhileWaiting)
}
Expand Down
32 changes: 18 additions & 14 deletions Sources/XCTestExtensions/XCTestExtensions.docc/XCTestExtensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,6 @@ app.deleteAndLaunch(withSpringboardAppName: "TestApp")
```


### Disable Password Autofill

The iOS Simulator has enabled password autofill by default, which can interfere with text entry in password fields in UI tests. The `XCTestCase` extension provides the `disablePasswordAutofill` to navigate to the settings app and disable password autofill.
```swift
class TestAppUITests: XCTestCase {
func testDeleteAndLaunch() throws {
disablePasswordAutofill()

// ...
}
}
```


### Text Entry

Allows a simple extension on `XCUIElement` to delete and type text in a text field or secure text field.
Expand All @@ -59,3 +45,21 @@ secureTextField.delete(count: 5)

Unfortunately, the iOS simulator sometimes has flaky behavior when entering text in a simulator with low computation resources.
The `enter(value:)` and `delete(count:)` methods provide the `checkIfTextWasEnteredCorrectly` and `checkIfTextWasDeletedCorrectly` parameters that are set to true by default to check if the values were entered correctly. If your text entry does fail to do so, e.g., an entry in a secure text field, set the `checkIfTextWasEnteredCorrectly` or `checkIfTextWasDeletedCorrectly` parameters to `false`.


## Topics

### Assertions

- ``XCTAssertThrowsErrorAsync``

### Text Entry

- ``XCTest/XCUIElement/enter(value:checkIfTextWasEnteredCorrectly:dismissKeyboard:)``
- ``XCTest/XCUIElement/delete(count:checkIfTextWasDeletedCorrectly:dismissKeyboard:)``

### App Interaction

- ``XCTest/XCUIApplication/deleteAndLaunch(withSpringboardAppName:)``
- ``XCTest/XCUIApplication/dismissKeyboard()``
- ``XCTest/XCUIApplication/homeScreenBundle``
6 changes: 4 additions & 2 deletions Tests/XCTestExtensionsTests/XCTestExtensionsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import XCTestExtensions


final class XCTestExtensionsTests: XCTestCase {
func testXCTestExtensions() throws {
XCTAssert(true)
func testXCTAssertThrowsPositive() async throws {
try await XCTAssertThrowsErrorAsync({ throw CancellationError() }()) { error in
_ = try XCTUnwrap(error as? CancellationError)
}
}
}

0 comments on commit 69eae1a

Please sign in to comment.