Hi @suzannaratcliff, I think fleshing out some concrete examples might help people get a better understanding of how these feature are intended to be used. And I would be curious to see a sketch of what some console output could theoretically look like when a “warning” issue is recorded.
If I am understanding this tool correctly, we would definitely make use of it, though I think we would prefer for there to be an “info” setting in addition to “warning” and “error”. @stephencelis and I work on a number of open source libraries that are used by thousands of developers, and we currently use withKnownIssue
and XCTExpectIssue
in order to approximate what I think this feature would bring to us. Let me try to concretely describe a simplified version of what we do.
We have a few tools in our ecosystem that allow one to “exhaustively” assert on how large pieces of state change after some event is processed. It’s not important to know all of the details of how this code works, but suffice it to say that it allows you to write an assertion like the following:
process(.nextSpeakerButtonTapped) {
$0.speakerIndex = 1
$0.secondsElapsed = 30
}
process(.nextSpeakerButtonTapped) {
$0.speakerIndex = 2
$0.secondsElapsed = 60
}
process(.nextSpeakerButtonTapped) {
$0.alert = "This is the last speaker. End meeting early?"
}
The $0
in these trailing closures represents a large piece of state with many fields, and some of those fields are themselves quite complex. Further, it represents the state before the .nextSpeakerButtonTapped
event is processed, and it’s our job to mutate it into the state after the event is processed.
And this assertion forces you to exhaustively prove how the full piece of state changes. If you leave off a part:
process(.nextSpeakerButtonTapped) {
$0.speakerIndex = 2
// Forgot to assert this: $0.secondsElapsed = 60
}
…you get a nicely formatted test failure letting you know what did not match:
❌ Issue recorded: A state change does not match expectation: …
RecordMeeting.State(
alert: nil,
− secondsElapsed: 30,
+ secondsElapsed: 60,
speakerIndex: 2,
syncUp: SyncUp(…),
transcript: ""
)
(Expected: −, Actual: +)
But, sometimes these kinds of exhaustive assertions can be a pain. When the state is very large, and when the event causes many things to change, we have to make a lot of mutations to $0
even though we may only be interested in a small part of it.
This is why we further allow these tools to be configured with a “non-exhaustive” option, which allows you to assert on only the pieces of state you care about. In this mode $0
represents the state after the event is processed, and so any mutations you make to $0
must not actually change the value, otherwise you get an error.
But, to make it clear to the user that there are technically changes left un-asserted we emit an expected issue/failure letting them know what things were not asserted on:
☑️ Issue recorded: A state change does not match expectation: …
RecordMeeting.State(
alert: nil,
− secondsElapsed: 30,
+ secondsElapsed: 60,
speakerIndex: 2,
syncUp: SyncUp(…),
transcript: ""
)
(Expected: −, Actual: +)
I’m using the
emoji to denote this is an “issue” but did not actually cause a test failure, but in the Swift Testing logs it actually looks like a failure unless you really analyze the report:
◇ Test nextSpeaker() started.
✘ Test nextSpeaker() recorded a known issue at RecordMeetingTests.swift:216:19: Issue recorded
✘ Test nextSpeaker() passed after 0.016 seconds with 1 known issue.
Those “✘” icons are very misleading. The test did not actually fail.
And so, if we had this feature, we would convert our withKnownIssue
s to instead report a “warning” issue (though ideally an “info” issue). To us it represents a potential issue, in that there is some un-asserted state lurking in the shadows, but because they are run in the non-exhaustive mode it is not an actual test failure.
So, overall I’m a big +1!