Skip to content

Behavior validation docs #408

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
df277e7
Add detail to the API collection for expectations.
iamleeg May 8, 2024
00f66c6
Add an article on testing for errors
iamleeg May 8, 2024
8a91324
An article on asynchronous code
iamleeg May 9, 2024
cede7da
Add copyright boilerplate to the new files
iamleeg May 9, 2024
d3f3cca
Link to expect and require macros on first mention.
iamleeg May 10, 2024
8192e0f
Hard word wrap paragraphs.
iamleeg May 10, 2024
a2bf9c0
Specify which error the require macro throws.
iamleeg May 10, 2024
a0dfc5b
Rename Swift Testing -> swift-testing
iamleeg May 10, 2024
9c1e5dd
Separate out asynchronous calculation from test of result.
iamleeg May 10, 2024
8027b83
Replace "block" with "closure"
iamleeg May 10, 2024
864fc9b
Describe that a throwing test function detects errors and fails
iamleeg May 10, 2024
1876270
Replace tags with attributes in exception-testing example
iamleeg May 10, 2024
1cb63d0
Rewrite the error examples as to use a food truck-related example.
iamleeg May 13, 2024
03aee83
Rewrite async testing examples to look like a food truck
iamleeg May 13, 2024
a609530
Replace the API collection code snippet with a food truck example.
iamleeg May 13, 2024
59861d7
Replace final example with FoodTruck-related example.
iamleeg May 15, 2024
e32836f
Remove the note about #require(throws: Never.self).
iamleeg May 15, 2024
1074cbc
Remove effecting function from #expect macro body.
iamleeg May 15, 2024
1811c9f
Replace #expect() and #require() with double-backtick links
iamleeg May 15, 2024
a5134ac
Add "asynchronous" to the abstract for Expectations
iamleeg May 17, 2024
e594612
Fix line-wrapping.
iamleeg May 17, 2024
a9a17b5
Remove double spaces after periods. Except that one.
iamleeg May 17, 2024
fa9c4c2
Suggested changes to asynchronous code article.
iamleeg May 17, 2024
9f5d89a
Resolve issues in the article on errors.
iamleeg May 17, 2024
9eae71e
2 spaces in code snippet indentation
iamleeg May 23, 2024
9fe5b3f
Address recent feedback.
iamleeg May 23, 2024
a53d724
Swap the expected and actual values in the #expect example
iamleeg May 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 42 additions & 6 deletions Sources/Testing/Testing.docc/Expectations.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,49 @@ See https://swift.org/LICENSE.txt for license information
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
-->

Check for expected values and outcomes in tests.
Check for expected values, outcomes, and asynchronous events in tests.

## Overview

The testing library provides `#expect()` and `#require()` macros you use to
validate expected outcomes. To validate that an error is thrown, or _not_ thrown,
the testing library provides several overloads of the macros that you can use.
Use a ``Confirmation`` to confirm the occurrence of an asynchronous event that
you can't check directly using an expectation.
Use ``expect(_:_:sourceLocation:)`` and
``require(_:_:sourceLocation:)-5l63q`` macros to validate expected
outcomes. To validate that an error is thrown, or _not_ thrown, the
testing library provides several overloads of the macros that you can
use. For more information, see <doc:testing-for-errors-in-swift-code>.

Use a ``Confirmation`` to confirm the occurrence of an
asynchronous event that you can't check directly using an expectation.
For more information, see <doc:testing-asynchronous-code>.

### Validate your code's result

To validate that your code produces an expected value, use
``expect(_:_:sourceLocation:)``. ``expect(_:_:sourceLocation:)`` captures the
expression you pass, and provides detailed information when the code doesn't
satisfy the expectation.

```swift
@Test func calculatingOrderTotal() {
let calculator = OrderCalculator()
#expect(calculator.total(of: [3, 3]) == 7)
// Prints "Expectation failed: (calculator.total(of: [3, 3]) → 6) == 7"
}
```

Your test keeps running after ``expect(_:_:sourceLocation:)`` fails. To stop
the test when the code doesn't satisfy a requirement, use
``require(_:_:sourceLocation:)-5l63q`` instead:

```swift
@Test func returningCustomerRemembersUsualOrder() throws {
let customer = try #require(Customer(id: 123))
// The test runner doesn't reach this line if the customer is nil.
#expect(customer.usualOrder.countOfItems == 2)
}
```

``require(_:_:sourceLocation:)-5l63q`` throws an instance of
``ExpectationFailedError`` when your code fails to satisfy the requirement.

## Topics

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

### Checking that errors are thrown

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

### Confirming that asynchronous events occur

- ``<doc:testing-asynchronous-code>
- ``confirmation(_:expectedCount:fileID:filePath:line:column:_:)``
- ``Confirmation``

Expand Down
70 changes: 70 additions & 0 deletions Sources/Testing/Testing.docc/testing-asynchronous-code.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Testing asynchronous code

<!--
This source file is part of the Swift.org open source project

Copyright (c) 2024 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception

See https://swift.org/LICENSE.txt for license information
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
-->

Validate whether your code causes expected events to happen.

## Overview

The testing library integrates with Swift concurrency, meaning that in many
situations you can test asynchronous code using standard Swift
features. Mark your test function as `async` and, in the function
body, `await` any asynchronous interactions:

```swift
@Test func priceLookupYieldsExpectedValue() async {
let mozarellaPrice = await unitPrice(for: .mozarella)
#expect(mozarellaPrice == 3)
}
```

In more complex situations you can use ``Confirmation`` to discover whether an
expected event happens.

### Confirm that an event happens

Call ``confirmation(_:expectedCount:fileID:filePath:line:column:_:)``
in your asynchronous test function to create a `Confirmation` for the
expected event. In the trailing closure parameter, call the code under
test. Swift Testing passes a `Confirmation` as the parameter to the
block, which you call as a function in the event handler for the code under
test when the event you're testing for occurs:

```swift
@Test("OrderCalculator successfully calculates subtotal for no pizzas")
func subtotalForNoPizzas() async {
let calculator = OrderCalculator()
await confirmation() { confirmation in
calculator.successHandler = { _ in confirmation() }
_ = await calculator.subtotal(for: PizzaToppings(bases: []))
}
}
```

If you expect the event to happen more than once, set the
`expectedCount` parameter to the number of expected occurrences. The
test passes if the number of occurrences during the test matches the
expected count, and fails otherwise.

### Confirm that an event doesn't happen

To validate that a particular event doesn't occur during a test,
create a `Confirmation` with an expected count of `0`:

```swift
@Test func orderCalculatorEncountersNoErrors() async {
let calculator = OrderCalculator()
await confirmation(expectedCount: 0) { confirmation in
calculator.errorHandler = { _ in confirmation() }
calculator.subtotal(for: PizzaToppings(bases: []))
}
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Testing for errors in Swift code

<!--
This source file is part of the Swift.org open source project

Copyright (c) 2024 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception

See https://swift.org/LICENSE.txt for license information
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
-->

Ensure that your code handles errors in the way you expect.

## Overview

Write tests for your code that validate the conditions in which the
code throws errors, and the conditions in which it returns without
throwing an error. Use overloads of the ``expect(_:_:sourceLocation:)`` and
``require(_:_:sourceLocation:)-5l63q`` macros that check for errors.

### Validate that your code throws an expected error

Create a test function that `throws` and `try` the code under test.
If the code throws an error, then your test fails.

To check that the code under test throws a specific error, or to continue a
longer test function after the code throws an error, pass that error as the
first argument of ``expect(throws:_:sourcelocation:performing:)-1xr34``, and
pass a closure that calls the code under test:

```swift
@Test func cannotAddToppingToPizzaBeforeStartOfList() {
var order = PizzaToppings(bases: [.calzone, .deepCrust])
#expect(throws: PizzaToppings.Error.outOfRange) {
try order.add(topping: .mozarella, toPizzasIn: -1..<0)
}
}
```

If the closure completes without throwing an error, the testing library
records an issue. Other overloads of ``expect(_:_:sourceLocation:)`` let you
test that the code throws an error of a given type, or matches an arbitrary
Boolean test. Similar overloads of ``require(_:_:sourceLocation:)-5l63q`` stop
running your test if the code doesn't throw the expected error.

### Validate that your code doesn't throw an error

A test function that throws an error fails, which is usually sufficient for
testing that the code under test doesn't throw. If you need to record a
thrown error as an issue without stopping the test function, compare
the error to `Never`:

```swift
@Test func canAddToppingToPizzaInPositionZero() throws {
var order = PizzaToppings(bases: [.thinCrust, .thinCrust])
#expect(throws: Never.self) {
try order.add(topping: .caper, toPizzasIn: 0..<1)
}
let toppings = try order.toppings(forPizzaAt: 0)
#expect(toppings == [.caper])
}
```

If the closure throws _any_ error, the testing library records an issue.
If you need the test to stop when the code throws an error, include the
code inline in the test function instead of wrapping it in an
``expect(throws:_:sourcelocation:performing:)-1xr34`` block.