Stubbing requests using URLProtocol
A practical example of an API stub that works on Swift Playgrounds.
This is Yusuke Yamaguchi an iOS engineer.
The other day I boarded an escort ship named “Kaga” which was being publicly introduced at the Osaka port.
It was very surprising how everyone from WAVE (WAVE: A nickname given to the women in Japan’s Maritime Self-Defense Forces) was beautifully uniformed, but the most surprising thing was that washlets were attached to the ship’s lavatories. Moreover, the toilet seat foot stand was also attached, and the hand dryers were attached to the side of the hand wash as well.
I did not expect a ship’s installations to have evolved so far in the last 20 years.
Unfortunately, flushing the toilet itself still required the valve to be operated by me just like before… 😔
About the URLProtocol
Well, I think that in recent years most smartphone applications are assumed to interact with a server.
During the development of the application, the server will also probably be under construction. So, more often than not, we will employ mechanisms that return dummy responses.
There are various ways to return a dummy response, but in this article I’d like to show you an example of an implementation that uses the URLProtocol found inside the standard iOS SDK.
I think that it is pretty common to stub responses by using cloud services or implementing external libraries through CocoaPods and Carthage, but it is also possible to create stubs with just the standard SDK.
Since this functionality exists in the standard SDK, it can be freely used in Playgrounds.
This time I’ll present sample code that can actually be ran on Xcode 10.1 / Swift 4.2 / Playgrounds, but of course you can use Objective-C as well. (Since the Objective-C code will not be presented, please convert it accordingly.)
Overview of the URLProtocol class
Write a class that inherits from the URLProtocol class and override the necessary methods.
Returning true with `canInit(with:)` will instantiate it so that communication can be handled like a proxy server.
You can replace the actual communication processing at `startLoading`.
Since we are dealing with communication processing, there is a possibility that the process may be interrupted if you do not leave the Playground running, so write the following at the beginning:
Implement a class that inherits from the URLProtocol
At first, write the following code to activate the class.
Next, register the data you want to hook. (I will be using this structure so that I can group the definitions and easily look them over after.)
*Although it is omitted here, it is possible to toggle the hooks by adding a parameter.
Override the URLProtocol method
At first, override the class method `canInit(with:)`.
*If you are only logging network communications, you can output the log here and return false instead.
Then, override the methods that are required for using stubs.
Implementation of a container that defines the hook
This is the container that holds the previously registered hook definitions.
Describe the response of the stub
Since I also want to list the definitions of the JSON response, I will write them in a separate structure.
Make an HTTP Client
In this article we want to make everything work within Playgrounds, so I’ve made and HTTP Client myself.
Of course, for normal development you can use any library you want. (For example if you rather use Alamofire.)
When using `forEach` as it is, the execution might happen in parallel and end before we finish processing the transmission. This hinders the verification.
This has nothing to do with the main article itself, but defining it is useful as it will help us verify things that require an array of elements to be executed sequentially.
Let’s see if two success patterns (notice and maintenance), one error pattern (error), and one without a hook definition (notfound) behave as expected.
The stub for the response `notice` works 🍺
* Response JSON(notice): ["notice": [["title": Notification 1., "code": 100], ["title": Notification 2., "code": 102]]]
The stub for the response `maintenance` works 🍺
* Response JSON(maintenance): ["code": 100, "message": Now maintenance.]
The stub for the response `error` works 🍺
* Response Error: Error Domain=test.api.error Code=1 "(null)"
If it doesn’t exists in the stub definitions, we expect it to NOT be handled, which also works. 😍
* Response Other: nil
What do you think?
You can make API stubs quite easily, even as a standalone.
By the way, if you create a new Playground with Xcode and paste all the code snippets presented in this article, everything should work, so please give it a try!
Thank you for reading.
*This article was released in Japanese at Fenrir Engineers.