Skip to content

Commit a5b39aa

Browse files
authored
Behavior validation docs (#408)
Introduces additional documentation for expectations and confirmations. ### Checklist: - [x] Code and documentation should follow the style of the [Style Guide](https://github.com/apple/swift-testing/blob/main/Documentation/StyleGuide.md). - [x] If public symbols are renamed or modified, DocC references should be updated.
1 parent 004994f commit a5b39aa

File tree

3 files changed

+180
-6
lines changed

3 files changed

+180
-6
lines changed

Sources/Testing/Testing.docc/Expectations.md

+42-6
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,49 @@ See https://swift.org/LICENSE.txt for license information
1010
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
1111
-->
1212

13-
Check for expected values and outcomes in tests.
13+
Check for expected values, outcomes, and asynchronous events in tests.
1414

1515
## Overview
1616

17-
The testing library provides `#expect()` and `#require()` macros you use to
18-
validate expected outcomes. To validate that an error is thrown, or _not_ thrown,
19-
the testing library provides several overloads of the macros that you can use.
20-
Use a ``Confirmation`` to confirm the occurrence of an asynchronous event that
21-
you can't check directly using an expectation.
17+
Use ``expect(_:_:sourceLocation:)`` and
18+
``require(_:_:sourceLocation:)-5l63q`` macros to validate expected
19+
outcomes. To validate that an error is thrown, or _not_ thrown, the
20+
testing library provides several overloads of the macros that you can
21+
use. For more information, see <doc:testing-for-errors-in-swift-code>.
22+
23+
Use a ``Confirmation`` to confirm the occurrence of an
24+
asynchronous event that you can't check directly using an expectation.
25+
For more information, see <doc:testing-asynchronous-code>.
26+
27+
### Validate your code's result
28+
29+
To validate that your code produces an expected value, use
30+
``expect(_:_:sourceLocation:)``. ``expect(_:_:sourceLocation:)`` captures the
31+
expression you pass, and provides detailed information when the code doesn't
32+
satisfy the expectation.
33+
34+
```swift
35+
@Test func calculatingOrderTotal() {
36+
let calculator = OrderCalculator()
37+
#expect(calculator.total(of: [3, 3]) == 7)
38+
// Prints "Expectation failed: (calculator.total(of: [3, 3]) → 6) == 7"
39+
}
40+
```
41+
42+
Your test keeps running after ``expect(_:_:sourceLocation:)`` fails. To stop
43+
the test when the code doesn't satisfy a requirement, use
44+
``require(_:_:sourceLocation:)-5l63q`` instead:
45+
46+
```swift
47+
@Test func returningCustomerRemembersUsualOrder() throws {
48+
let customer = try #require(Customer(id: 123))
49+
// The test runner doesn't reach this line if the customer is nil.
50+
#expect(customer.usualOrder.countOfItems == 2)
51+
}
52+
```
53+
54+
``require(_:_:sourceLocation:)-5l63q`` throws an instance of
55+
``ExpectationFailedError`` when your code fails to satisfy the requirement.
2256

2357
## Topics
2458

@@ -30,6 +64,7 @@ you can't check directly using an expectation.
3064

3165
### Checking that errors are thrown
3266

67+
- <doc:testing-for-errors-in-swift-code>
3368
- ``expect(throws:_:sourceLocation:performing:)-79piu``
3469
- ``expect(throws:_:sourceLocation:performing:)-1xr34``
3570
- ``expect(_:sourceLocation:performing:throws:)``
@@ -41,6 +76,7 @@ you can't check directly using an expectation.
4176

4277
### Confirming that asynchronous events occur
4378

79+
- ``<doc:testing-asynchronous-code>
4480
- ``confirmation(_:expectedCount:fileID:filePath:line:column:_:)``
4581
- ``Confirmation``
4682

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Testing asynchronous code
2+
3+
<!--
4+
This source file is part of the Swift.org open source project
5+
6+
Copyright (c) 2024 Apple Inc. and the Swift project authors
7+
Licensed under Apache License v2.0 with Runtime Library Exception
8+
9+
See https://swift.org/LICENSE.txt for license information
10+
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
11+
-->
12+
13+
Validate whether your code causes expected events to happen.
14+
15+
## Overview
16+
17+
The testing library integrates with Swift concurrency, meaning that in many
18+
situations you can test asynchronous code using standard Swift
19+
features. Mark your test function as `async` and, in the function
20+
body, `await` any asynchronous interactions:
21+
22+
```swift
23+
@Test func priceLookupYieldsExpectedValue() async {
24+
let mozarellaPrice = await unitPrice(for: .mozarella)
25+
#expect(mozarellaPrice == 3)
26+
}
27+
```
28+
29+
In more complex situations you can use ``Confirmation`` to discover whether an
30+
expected event happens.
31+
32+
### Confirm that an event happens
33+
34+
Call ``confirmation(_:expectedCount:fileID:filePath:line:column:_:)``
35+
in your asynchronous test function to create a `Confirmation` for the
36+
expected event. In the trailing closure parameter, call the code under
37+
test. Swift Testing passes a `Confirmation` as the parameter to the
38+
block, which you call as a function in the event handler for the code under
39+
test when the event you're testing for occurs:
40+
41+
```swift
42+
@Test("OrderCalculator successfully calculates subtotal for no pizzas")
43+
func subtotalForNoPizzas() async {
44+
let calculator = OrderCalculator()
45+
await confirmation() { confirmation in
46+
calculator.successHandler = { _ in confirmation() }
47+
_ = await calculator.subtotal(for: PizzaToppings(bases: []))
48+
}
49+
}
50+
```
51+
52+
If you expect the event to happen more than once, set the
53+
`expectedCount` parameter to the number of expected occurrences. The
54+
test passes if the number of occurrences during the test matches the
55+
expected count, and fails otherwise.
56+
57+
### Confirm that an event doesn't happen
58+
59+
To validate that a particular event doesn't occur during a test,
60+
create a `Confirmation` with an expected count of `0`:
61+
62+
```swift
63+
@Test func orderCalculatorEncountersNoErrors() async {
64+
let calculator = OrderCalculator()
65+
await confirmation(expectedCount: 0) { confirmation in
66+
calculator.errorHandler = { _ in confirmation() }
67+
calculator.subtotal(for: PizzaToppings(bases: []))
68+
}
69+
}
70+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Testing for errors in Swift code
2+
3+
<!--
4+
This source file is part of the Swift.org open source project
5+
6+
Copyright (c) 2024 Apple Inc. and the Swift project authors
7+
Licensed under Apache License v2.0 with Runtime Library Exception
8+
9+
See https://swift.org/LICENSE.txt for license information
10+
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
11+
-->
12+
13+
Ensure that your code handles errors in the way you expect.
14+
15+
## Overview
16+
17+
Write tests for your code that validate the conditions in which the
18+
code throws errors, and the conditions in which it returns without
19+
throwing an error. Use overloads of the ``expect(_:_:sourceLocation:)`` and
20+
``require(_:_:sourceLocation:)-5l63q`` macros that check for errors.
21+
22+
### Validate that your code throws an expected error
23+
24+
Create a test function that `throws` and `try` the code under test.
25+
If the code throws an error, then your test fails.
26+
27+
To check that the code under test throws a specific error, or to continue a
28+
longer test function after the code throws an error, pass that error as the
29+
first argument of ``expect(throws:_:sourcelocation:performing:)-1xr34``, and
30+
pass a closure that calls the code under test:
31+
32+
```swift
33+
@Test func cannotAddToppingToPizzaBeforeStartOfList() {
34+
var order = PizzaToppings(bases: [.calzone, .deepCrust])
35+
#expect(throws: PizzaToppings.Error.outOfRange) {
36+
try order.add(topping: .mozarella, toPizzasIn: -1..<0)
37+
}
38+
}
39+
```
40+
41+
If the closure completes without throwing an error, the testing library
42+
records an issue. Other overloads of ``expect(_:_:sourceLocation:)`` let you
43+
test that the code throws an error of a given type, or matches an arbitrary
44+
Boolean test. Similar overloads of ``require(_:_:sourceLocation:)-5l63q`` stop
45+
running your test if the code doesn't throw the expected error.
46+
47+
### Validate that your code doesn't throw an error
48+
49+
A test function that throws an error fails, which is usually sufficient for
50+
testing that the code under test doesn't throw. If you need to record a
51+
thrown error as an issue without stopping the test function, compare
52+
the error to `Never`:
53+
54+
```swift
55+
@Test func canAddToppingToPizzaInPositionZero() throws {
56+
var order = PizzaToppings(bases: [.thinCrust, .thinCrust])
57+
#expect(throws: Never.self) {
58+
try order.add(topping: .caper, toPizzasIn: 0..<1)
59+
}
60+
let toppings = try order.toppings(forPizzaAt: 0)
61+
#expect(toppings == [.caper])
62+
}
63+
```
64+
65+
If the closure throws _any_ error, the testing library records an issue.
66+
If you need the test to stop when the code throws an error, include the
67+
code inline in the test function instead of wrapping it in an
68+
``expect(throws:_:sourcelocation:performing:)-1xr34`` block.

0 commit comments

Comments
 (0)