In my last post, where I refactored a typical download-and-parse workflow, I said this about the downloader:
I probably didn't write that as correctly as I should. It's not that I should have written the downloader to be more testable, I should have written a test-friendly downloading pattern. The JSON downloader in the previous post can't really be tested, because the golden rule is once you leave your app (in this case, for a network request) you are not longer UNIT testing.
What I would want to do is make sure the code that is requesting downloaded data should be written so that it is not dependent on a specific downloader. If I'm testing I'd like to make my code use a different downloader, which might instead read from a file, or a plist or just make stuff up. That way I avoid the network request.
Ok, so how would I do this? I'd like to have something like this:
And now maybe I'll add a tester downloader
So then I could write a downloader function like this:
So now I can either download correctly, or use the test downloader when I’m running unit tests:
This doesn’t work. We get the dreaded “Protocol can only be used as a generic constraint….” error you’ve probably seen a thousand times.
To get around this I need to use Type Erasure, which is where everything gets much larger and that’s why this problem is a whole new subject, hence why I avoided it in the last blog post.
I won’t go into the details of type erasure, it’s huge. But you need to create a new struct that implements the protocol and acts as a proxy, it’s just a whole load of complicated stuff. This is a great video explaining it: https://academy.realm.io/posts/altconf-hector-matos-type-erasure-magic/
But the solution is the following, I make the type erased struct:
and make my downloader method using the type erased type:
Now everything’s more testable.
Additionally I now have scope for multiple testable scenarios. I can have a downloader that fails, perhaps with a given error, a downloader that succeeds with an empty result, a downloader that reads from a file and returns the parsed data, whatever I can think of, this can help test corner cases and failure scenarios that previously would have been left out of testing.