I have some code at the boundary of my app that takes input from a library. The library has some guarentees that are documented in the user documentation, but which aren't enforceable by the compiler (e.g. "this array will never be empty", "the members of this array each have a unique id
", etc.).
I see three ways of handling this:
- Don't check these assumptions are true, just assume they are. This leaves me vulnerable to mysterious bugs in case that library ever makes breaking changes.
- Validate those assumptions by throwing an error if they're not met. This is great, acts like a detector for sneaky breaking changes in the library, and it's trivial to test. However, it proliferates
throws
andtry
all over my code, for functions that otherwise have no need to throw. - Validate those assumptions using
assert
/precondition
/fatalError
and such. This seems like a good trade off. The assumptions are documented and validated in code, and the call sites don't have to be paranoid about handling errors that'll almost never actually happen.
I like approach 3, but it was tricky to test. I've made wrappers for assert
/assertionFailure
/precondition
that let me intercept these calls and stub them in tests (but not yet preconditionFailure
or fatalError
, since those return Never
and are much trickier to stub). This works for all of my own calls to these functions.
This works so far, but then I realized I have untested code paths related to, say, Dictionary.init(uniqueKeysWithValues:)
. Every call to this function is encoding an assumption that the input will have unique keys for all values. If not, it'll explode. I'm looking for a way to try to cover that case in my tests, but I can't find a way to "override" the trapping behavior that's done in this function.
I can replace each such call with Dictionary.init(_:uniquingKeysWith:)
, where the block calls my stubbed assert
, but this really pollutes my code. Is there a way to improve this?
I read some threads about this, where a common point was that the trapping behavior (e.g. of the subscript operator of Arrays) is intentionally not indirect (therefor not intercept-able) out of performance considerations. Fair enough, but are all standard library traps equally rigid?
TL;DR: What's the best way to test code that defends against invalid input using assert
and friends, without replacing it with throws
/try
all over the code base?