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
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
tryall over my code, for functions that otherwise have no need to throw.
- Validate those assumptions using
fatalErrorand 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
precondition that let me intercept these calls and stub them in tests (but not yet
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
try all over the code base?