|
| 1 | +# Describing and reflecting values |
| 2 | + |
| 3 | +<!-- |
| 4 | +This source file is part of the Swift.org open source project |
| 5 | +
|
| 6 | +Copyright (c) 2024–2026 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 | +Add custom descriptions and mirrors to values you use in your tests. |
| 14 | + |
| 15 | +## Overview |
| 16 | + |
| 17 | +The testing library provides two protocols, ``CustomTestStringConvertible`` and |
| 18 | +``CustomTestReflectable``, that you can use to customize the appearance of |
| 19 | +values in Swift. The testing library uses these protocols to describe |
| 20 | +parameterized test arguments and, if a call to ``expect(_:_:sourceLocation:)`` |
| 21 | +or ``require(_:_:sourceLocation:)-5l63q`` fails, to describe any values you pass |
| 22 | +to them. |
| 23 | + |
| 24 | +## Customize the description of a value |
| 25 | + |
| 26 | +You use the ``CustomTestStringConvertible`` protocol when you want to customize |
| 27 | +the description of a value _during testing only_. Values whose types conform to |
| 28 | +this protocol use it to describe themselves when the testing library presents |
| 29 | +them as part of the output of a test. For example, this protocol affects the |
| 30 | +display of values you pass as arguments to test functions or that are elements |
| 31 | +of an expectation failure. |
| 32 | + |
| 33 | +By default, the testing library converts values to strings using |
| 34 | +[`String(describing:)`](https://developer.apple.com/documentation/swift/string/init(describing:)-67ncf). |
| 35 | +The resulting string may be inappropriate for some types and their values. If |
| 36 | +you make the type of the value conform to ``CustomTestStringConvertible``, then |
| 37 | +the testing library will use the value of its ``CustomTestStringConvertible/testDescription`` |
| 38 | +property instead. |
| 39 | + |
| 40 | +For example, consider the following type: |
| 41 | + |
| 42 | +```swift |
| 43 | +enum Food: CaseIterable { |
| 44 | + case paella, oden, ragu |
| 45 | +} |
| 46 | +``` |
| 47 | + |
| 48 | +If you pass an array of cases from this enumeration to a parameterized test |
| 49 | +function: |
| 50 | + |
| 51 | +```swift |
| 52 | +@Test(arguments: Food.allCases) |
| 53 | +func isDelicious(_ food: Food) { ... } |
| 54 | +``` |
| 55 | + |
| 56 | +Then the testing library needs to present all elements in the array in its |
| 57 | +output, but the default description of these values may not be adequately |
| 58 | +descriptive: |
| 59 | + |
| 60 | +``` |
| 61 | +◇ Test case passing 1 argument food → .paella to isDelicious(_:) started. |
| 62 | +◇ Test case passing 1 argument food → .oden to isDelicious(_:) started. |
| 63 | +◇ Test case passing 1 argument food → .ragu to isDelicious(_:) started. |
| 64 | +``` |
| 65 | + |
| 66 | +When you adopt ``CustomTestStringConvertible``, you can include customized |
| 67 | +descriptions in your test output instead. |
| 68 | + |
| 69 | +```swift |
| 70 | +extension Food: CustomTestStringConvertible { |
| 71 | + var testDescription: String { |
| 72 | + switch self { |
| 73 | + case .paella: |
| 74 | + "paella valenciana" |
| 75 | + case .oden: |
| 76 | + "おでん" |
| 77 | + case .ragu: |
| 78 | + "ragù alla bolognese" |
| 79 | + } |
| 80 | + } |
| 81 | +} |
| 82 | +``` |
| 83 | + |
| 84 | +The testing library then uses ``CustomTestStringConvertible/testDescription`` to |
| 85 | +present these values: |
| 86 | + |
| 87 | +``` |
| 88 | +◇ Test case passing 1 argument food → paella valenciana to isDelicious(_:) started. |
| 89 | +◇ Test case passing 1 argument food → おでん to isDelicious(_:) started. |
| 90 | +◇ Test case passing 1 argument food → ragù alla bolognese to isDelicious(_:) started. |
| 91 | +``` |
| 92 | + |
| 93 | +## Customize the reflection of a value |
| 94 | + |
| 95 | +When a call to ``expect(_:_:sourceLocation:)`` or to ``require(_:_:sourceLocation:)-5l63q`` |
| 96 | +fails, the testing library presents the value or values you pass to these macros |
| 97 | +in its output. |
| 98 | + |
| 99 | +The testing library uses [`Mirror.init(reflecting:)`](https://developer.apple.com/documentation/swift/mirror/init(reflecting:)) |
| 100 | +to break down these values if they contain properties that may be of interest to |
| 101 | +you. For instance, if the `isDelicious(_:)` test fails, you might see output |
| 102 | +such as: |
| 103 | + |
| 104 | +``` |
| 105 | +✘ Test isDelicious(_:) recorded an issue with 1 argument food → sandwich |
| 106 | +↳ food.isDelicious → false |
| 107 | +↳ food → sandwich |
| 108 | +↳ sandwich → (toppings: [Food.pickles, Food.candyCorn]) |
| 109 | +↳ toppings → [Food.pickles, Food.candyCorn] |
| 110 | +↳ isDelicious → false |
| 111 | +``` |
| 112 | + |
| 113 | +This output is expressive, but also contains redundant information. You can |
| 114 | +refine it further by making `Food` conform to the ``CustomTestReflectable`` |
| 115 | +protocol. |
| 116 | + |
| 117 | +```swift |
| 118 | +extension Food: CustomTestReflectable { |
| 119 | + var customTestMirror: Mirror { |
| 120 | + switch self { |
| 121 | + case let .sandwich(toppings): |
| 122 | + let ingredientNames = toppings.map { String(describingForTest: $0) } |
| 123 | + return Mirror( |
| 124 | + self, |
| 125 | + children: [(label: "toppings", value: ingredientNames)] |
| 126 | + ) |
| 127 | + default: |
| 128 | + Mirror(self, children: []) |
| 129 | + } |
| 130 | + } |
| 131 | +} |
| 132 | +``` |
| 133 | + |
| 134 | +With this conformance, the output of the failed test is instead: |
| 135 | + |
| 136 | +``` |
| 137 | +✘ Test isDelicious(_:) recorded an issue with 1 argument food → sandwich |
| 138 | +↳ food.isDelicious → false |
| 139 | +↳ food → sandwich |
| 140 | +↳ toppings → ["pickles", "candy corn"] |
| 141 | +↳ isDelicious → false |
| 142 | +``` |
| 143 | + |
| 144 | +## Implement custom descriptions using private properties |
| 145 | + |
| 146 | +If part or all of your type's state is `private` or otherwise not visible to |
| 147 | +your test target, you may not be able to implement ``CustomTestStringConvertible/testDescription`` |
| 148 | +or ``CustomTestReflectable/customTestMirror`` in your test target. You can |
| 149 | +implement these properties, without adding conformances to either protocol, in |
| 150 | +your production target, and then add empty protocol conformances in your test |
| 151 | +target. Make sure to use `internal` or `package` visibility for the properties |
| 152 | +so that your test target is able to use them. |
| 153 | + |
| 154 | +```swift |
| 155 | +// In your production target: |
| 156 | + |
| 157 | +extension Food { |
| 158 | + package var testDescription: String { ... } |
| 159 | + package var customTestMirror: Mirror { ... } |
| 160 | +} |
| 161 | + |
| 162 | +// In your test target: |
| 163 | + |
| 164 | +import FoodTruck |
| 165 | + |
| 166 | +extension Food: CustomTestStringConvertible, CustomTestReflectable {} |
| 167 | +``` |
| 168 | + |
| 169 | +- Note: If you use `internal` visibility for these properties, you must import |
| 170 | + your production target into your test target using the `@testable` attribute. |
0 commit comments