Skip to content

Commit 2ac7d82

Browse files
committed
Adopt typed throws in the implementation of #expect()/#require().
This PR adopts typed throws (`throws(E)` instead of `throws`) in the `#expect()` and `#require()` macros' declarations and in their implementations. In particular, the return type of the implementation functions changes from `Result<R, any Error>` to `Result<R, ExpectationFailedError>` and all implementation functions that take a closure have been updated with an additional generic `E` parameter. `#expect(throws:)` and `#require(throws:)` take two generic error types, `EExpected` and `EActual`. The former is the error type specified by the test author, while the latter is the actual error type inferred from the body closure. In common usage, both will resolve to `any Error`. The deprecated `#expect {} throws: {}` and `#require {} throws: {}` variants are marked `@_unavailableInEmbedded` and continue to use `any Error`.
1 parent 87f4168 commit 2ac7d82

File tree

7 files changed

+282
-194
lines changed

7 files changed

+282
-194
lines changed

Sources/Testing/ExitTests/ExitTest.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ func callExitTest(
466466
isRequired: Bool,
467467
isolation: isolated (any Actor)? = #isolation,
468468
sourceLocation: SourceLocation
469-
) async -> Result<ExitTest.Result?, any Error> {
469+
) async -> Result<ExitTest.Result?, ExpectationFailedError> {
470470
guard let configuration = Configuration.current ?? Configuration.all.first else {
471471
preconditionFailure("A test must be running on the current task to use #expect(processExitsWith:).")
472472
}

Sources/Testing/Expectations/Expectation+Macro.swift

Lines changed: 85 additions & 80 deletions
Large diffs are not rendered by default.

Sources/Testing/Expectations/ExpectationChecking+Macro.swift

Lines changed: 160 additions & 103 deletions
Large diffs are not rendered by default.

Sources/Testing/Support/Additions/ResultAdditions.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ extension Result {
1919
///
2020
/// - Warning: This function is used to implement the `#expect()` and
2121
/// `#require()` macros. Do not call it directly.
22-
@inlinable public func __required() throws -> Success {
22+
@inlinable public func __required() throws(Failure) -> Success {
2323
try get()
2424
}
2525
}
@@ -48,7 +48,7 @@ extension Result {
4848
///
4949
/// - Warning: This function is used to implement the `#expect()` and
5050
/// `#require()` macros. Do not call it directly.
51-
@discardableResult public func __required<T>() throws -> T where Success == T? {
51+
@discardableResult public func __required<T>() throws(any Error) -> T where Success == T? {
5252
guard let result = try get() else {
5353
throw APIMisuseError(description: "Could not unwrap 'nil' value of type Optional<\(T.self)>. Consider using #expect() instead of #require() here.")
5454
}

Sources/Testing/Testing.docc/Expectations.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,11 @@ the test when the code doesn't satisfy a requirement, use
6565
### Checking that errors are thrown
6666

6767
- <doc:testing-for-errors-in-swift-code>
68-
- ``expect(throws:_:sourceLocation:performing:)-1hfms``
69-
- ``expect(throws:_:sourceLocation:performing:)-7du1h``
68+
- ``expect(throws:_:sourceLocation:performing:)-7p3ic``
69+
- ``expect(throws:_:sourceLocation:performing:)-3y3oo``
7070
- ``expect(_:sourceLocation:performing:throws:)``
71-
- ``require(throws:_:sourceLocation:performing:)-7n34r``
72-
- ``require(throws:_:sourceLocation:performing:)-4djuw``
71+
- ``require(throws:_:sourceLocation:performing:)-1zgzw``
72+
- ``require(throws:_:sourceLocation:performing:)-5qhyv``
7373
- ``require(_:sourceLocation:performing:throws:)``
7474

7575
### Checking how processes exit

Sources/Testing/Testing.docc/testing-for-errors-in-swift-code.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ If the code throws an error, then your test fails.
2626

2727
To check that the code under test throws a specific error, or to continue a
2828
longer test function after the code throws an error, pass that error as the
29-
first argument of ``expect(throws:_:sourceLocation:performing:)-7du1h``, and
29+
first argument of ``expect(throws:_:sourceLocation:performing:)-3y3oo``, and
3030
pass a closure that calls the code under test:
3131

3232
```swift
@@ -48,8 +48,8 @@ running your test if the code doesn't throw the expected error.
4848

4949
To check that the code under test throws an error of any type, pass
5050
`(any Error).self` as the first argument to either
51-
``expect(throws:_:sourceLocation:performing:)-1hfms`` or
52-
``require(throws:_:sourceLocation:performing:)-7n34r``:
51+
``expect(throws:_:sourceLocation:performing:)-7p3ic`` or
52+
``require(throws:_:sourceLocation:performing:)-1zgzw``:
5353

5454
```swift
5555
@Test func cannotAddToppingToPizzaBeforeStartOfList() {
@@ -81,7 +81,7 @@ the error to `Never`:
8181
If the closure throws _any_ error, the testing library records an issue.
8282
If you need the test to stop when the code throws an error, include the
8383
code inline in the test function instead of wrapping it in a call to
84-
``expect(throws:_:sourceLocation:performing:)-7du1h``.
84+
``expect(throws:_:sourceLocation:performing:)-3y3oo``.
8585

8686
## Inspect an error thrown by your code
8787

Tests/TestingTests/IssueTests.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,12 +511,14 @@ final class IssueTests: XCTestCase {
511511

512512
let randomNumber = Int.random(in: 0 ... .max)
513513
await Test {
514+
#if !hasFeature(Embedded)
514515
#expect {
515516
asyncNotRequired()
516517
throw MyError()
517518
} throws: {
518519
$0 is MyError
519520
}
521+
#endif
520522
#expect(throws: MyError.self) {
521523
asyncNotRequired()
522524
throw MyError()
@@ -541,7 +543,11 @@ final class IssueTests: XCTestCase {
541543

542544
func testErrorCheckingWithExpect_Mismatching() async throws {
543545
let expectationFailed = expectation(description: "Expectation failed")
546+
#if !hasFeature(Embedded)
544547
expectationFailed.expectedFulfillmentCount = 13
548+
#else
549+
expectationFailed.expectedFulfillmentCount = 10
550+
#endif
545551

546552
var configuration = Configuration()
547553
configuration.eventHandler = { event, _ in
@@ -559,11 +565,13 @@ final class IssueTests: XCTestCase {
559565
let randomNumber = Int.random(in: 0 ... .max)
560566

561567
await Test {
568+
#if !hasFeature(Embedded)
562569
#expect {
563570
asyncNotRequired()
564571
} throws: {
565572
$0 is MyError
566573
}
574+
#endif
567575
#expect(throws: MyError.self) {
568576
asyncNotRequired()
569577
}
@@ -582,11 +590,13 @@ final class IssueTests: XCTestCase {
582590
#expect(throws: MyError()) {
583591
throw MyDescriptiveError(description: "something wrong")
584592
}
593+
#if !hasFeature(Embedded)
585594
#expect {
586595
throw MyDescriptiveError(description: "something wrong")
587596
} throws: {
588597
_ in false
589598
}
599+
#endif
590600
#expect(throws: Never.self) {
591601
throw MyError()
592602
}
@@ -600,12 +610,14 @@ final class IssueTests: XCTestCase {
600610
#expect(throws: MyError.self) {
601611
try nonVoidReturning()
602612
}
613+
#if !hasFeature(Embedded)
603614
#expect {
604615
throw MyError()
605616
} throws: { error in
606617
let parameterizedError = try #require(error as? MyParameterizedError)
607618
return parameterizedError.index == 123
608619
}
620+
#endif
609621
}.run(configuration: configuration)
610622

611623
await fulfillment(of: [expectationFailed], timeout: 0.0)
@@ -681,11 +693,13 @@ final class IssueTests: XCTestCase {
681693

682694
let randomNumber = Int.random(in: 0 ... .max)
683695
await Test {
696+
#if !hasFeature(Embedded)
684697
await #expect { () async throws in
685698
throw MyError()
686699
} throws: {
687700
$0 is MyError
688701
}
702+
#endif
689703
await #expect(throws: MyError.self) { () async throws in
690704
throw MyError()
691705
}
@@ -708,7 +722,11 @@ final class IssueTests: XCTestCase {
708722

709723
func testErrorCheckingWithExpectAsync_Mismatching() async throws {
710724
let expectationFailed = expectation(description: "Expectation failed")
725+
#if !hasFeature(Embedded)
711726
expectationFailed.expectedFulfillmentCount = 13
727+
#else
728+
expectationFailed.expectedFulfillmentCount = 10
729+
#endif
712730

713731
var configuration = Configuration()
714732
configuration.eventHandler = { event, _ in
@@ -724,9 +742,11 @@ final class IssueTests: XCTestCase {
724742

725743
let randomNumber = Int.random(in: 0 ... .max)
726744
await Test {
745+
#if !hasFeature(Embedded)
727746
await #expect { () async in } throws: {
728747
$0 is MyError
729748
}
749+
#endif
730750
await #expect(throws: MyError.self) { () async in }
731751
await #expect(throws: MyParameterizedError(index: randomNumber)) { () async in }
732752
await #expect(throws: MyError.self) { () async throws in
@@ -741,11 +761,13 @@ final class IssueTests: XCTestCase {
741761
await #expect(throws: MyError()) { () async throws in
742762
throw MyDescriptiveError(description: "something wrong")
743763
}
764+
#if !hasFeature(Embedded)
744765
await #expect { () async throws in
745766
throw MyDescriptiveError(description: "something wrong")
746767
} throws: {
747768
_ in false
748769
}
770+
#endif
749771
await #expect(throws: Never.self) { () async throws in
750772
throw MyError()
751773
}
@@ -759,12 +781,14 @@ final class IssueTests: XCTestCase {
759781
await #expect(throws: MyError.self) {
760782
try await nonVoidReturning()
761783
}
784+
#if !hasFeature(Embedded)
762785
await #expect { () async throws in
763786
throw MyError()
764787
} throws: { error in
765788
let parameterizedError = try #require(error as? MyParameterizedError)
766789
return parameterizedError.index == 123
767790
}
791+
#endif
768792
}.run(configuration: configuration)
769793

770794
await fulfillment(of: [expectationFailed], timeout: 0.0)
@@ -822,6 +846,7 @@ final class IssueTests: XCTestCase {
822846
await fulfillment(of: [expectationFailed], timeout: 0.0)
823847
}
824848

849+
#if !hasFeature(Embedded)
825850
func testErrorCheckingWithExpect_ThrowingFromErrorMatcher() async throws {
826851
let errorCaught = expectation(description: "Error matcher's error caught")
827852
let expectationFailed = expectation(description: "Expectation failed")
@@ -931,6 +956,7 @@ final class IssueTests: XCTestCase {
931956

932957
await fulfillment(of: [errorCaught, expectationFailed], timeout: 0.0)
933958
}
959+
#endif
934960

935961
func testErrorCheckingWithExpect_ResultValue() throws {
936962
let error = #expect(throws: MyDescriptiveError.self) {

0 commit comments

Comments
 (0)