URLSession: Stubbing Network Responses for Unit Tests

Photo by Jordan Harrison on Unsplash

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.

All of what we want to achieve can be done so using Apple’s trusty, URLSession. More particularly, the URLSession’s configuration property, URLSessionConfiguration. Let’s take a look.

Our 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 subclassURLProtocol

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 didReceive didLoad and finally didFinishLoading

OK last bit of setup, the URLSession.

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 NameService

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 headerFields and 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 requestHandler

Here you can see I gave it a statusCode of 304, which is validated on my Networking Client dataTask response.

Conclusion;

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.

Lead iOS Engineer  — Advanced Github downloader — Serial StackOverflow browser