Unit Tests, by definition, are developed to test a single unit of code, in isolation.
Unit Tests should never call out to a ‘real’ network, and i’m here to show you how you can easily stub your network responses so you can continue to test your ‘unit of code’ behaviour upon receipt of pre-defined, stubbed responses.
URLSessionConfiguration allows us to define additional
URLProtocol's which in turn, define the behaviour of requests. Sounds the perfect place, so let’s get stuck in.
Let’s go ahead, and in our Unit Test target, add a new class,
MockUrlProtocol which will subclass
Here we can populate the class with a couple of properties;
Later, we can stub the
error property with one of our choosing, and you’ll see how this is interpreted later.
requestHandler, is where, per request, we can specify our own request handler, taking in a
HTTPURLResponse, and response data (our JSON response). Again, more on this later.
The rest of the implementation for our URLProtocol subclass is quite boiler plate, so here, have it FOC 😉
To a casual Swift developer, the above should be quite comprehendible, however, if you are unsure what each of the methods relate to, please check the Apple Docs here:
Still following? Ace. So now we have a basic implementation of a URLProtocol subclass, where we can define a straight up error, handled on the classes
startLoading function, and providing that's cool, and we have a
requestHandler set, we attempt to handle the response, and call the required delegate methods down the chain
didLoad and finally
OK last bit of setup, the
For every new ViewModel I create, which depends on some network interaction, I use my own Networking Protocol;
My protocol takes a
urlSessionproperty, perfect for what we need here. Your implementation, your DI, your shoe-horned code may differ, you just need to be able to give a different
URLSession when not/testing.
To create a custom
URLSession we can use with our tests, using our lovingly created
MockURLProtocol looks a little like this:
Let’s talk our
MockNetworking struct through. Typically, at runtime, you may use
URLSession.shared to interface with a
URLSession object and make network requests, perfectly fine, but right now, for Unit Tests, we need to create a
MockNetworking object, with its own
URLSession as per the protocol, where we have told the
URLSession to use our custom
URLProtocol via the
protocolClasses property on the configuration.
See it falling into place? Right, now read that last paragraph again so it makes sense.
The above implementation, you’ll see uses the
ephermeral configuration, as opposed to
default and thats fully intentional.
Ephermeral uses only RAM for cache, no persistent storage for anything like cookies etc. Last thing you want, is device cache getting in the way of some good unit tests.
Cool so you are all set to go, let’s put it into practice.
My app requires me to fetch names from a server, it’s pretty boring, but essential and I want to make sure our service is handling errors correctly.
Here is my implementation of
The focus here is not on the implementation of my NameService, the endpoint isn’t even real, just it has a public
getNames method, that uses a simple closure, and it takes a
APIClientProtocol instance as a dependency.
NameService here doesn’t care for what URLSession setup we are using, and I admire this level of abstraction. Scroll up, you will see we our
MockNetworking struct conforms to
APIClientProtocol and we can just throw an instance of that, into the NameService, for testing 😜
Ready to test? Let’s go.
Declare a new test method, this one for the success (happy path)
Line up your JSON Structure (or which ever format your response should be in). This can be read from a .txt file if you desire, but for the purpose of this, i’ll just freehand it in the test:
Create a Data object from the response;
let data = response.data(using: .utf8)!
Now, let’s create that request handler. Here’s where we describe the response to our URLSession:
OK so here the URL has to match, we can stub the
statusCode in, and the usual suspects like
httpVersion. Perfect. Now populate the rest of your test:
You can now flip it, and test that your/my
didError closure from the NameViewer class is called, on failure with the following
Here you can see I gave it a statusCode of 304, which is validated on my Networking Client dataTask response.
Appreciate theres a few things that go into making this work, but I think it’s quite neat, do you? Once your
URLProtocol is setup, and you configure a mock
URLSession instance for testing, the rest is easy, it doesn’t interact with normal running of your app, and you can easily test your ViewModels behave correctly when service responses are good/bad.
What do you think?
If it helps to understand, check out my open source Swift Networking Lib, SimplyNetworking. It includes this concept above.
Next up, I think i’ll tackle the same concept above but for
GraphQL — given i’ve become quite verse in
GraphQL these last 12–15 months.