Unit Test Networking Code in Swift Without Making Loads of Mock Classes

Yugantar Jain
Dev Genius
Published in
4 min readJul 27, 2020

--

Subclassing URLProtocol to mock URLSession network requests

Photo by Alina Grubnyak on Unsplash

Hi,

In this article, I share a neat method to unit test networking code in Swift; where we’ll implement mocking of URLSession network requests without having to build loads of mock classes for each API.

Purpose of Mocking Network Requests

While unit testing networking code, it is a standard industry best-practice to mock the network request and not actually communicate with the server.

There are many reasons for this, some of which are listed below-

  • Unit Tests are supposed to be fast and reliable. Actually communicating with the server won’t only make the unit tests extremely slow, but also unreliable due to dependence on the internet connection.
  • Actually communicating with the server would disrupt the backend. Example: Let’s say you are unit testing networking code to delete a user. Making an actual call to the server would lead to actual deletion of the user, we don’t want that :P

Implementation

Note: this article and implementation is meant for URLSession in Swift only.

We’ll be using Combine dataTaskPublisher for URLSession in this article. However, we can also use the standard URLSession.dataTask instead. The concept remains the same.

Let’s get started!

1. REST API Networking Class (starting point)

Let’s say we are using a backend REST API to load the profile of the user.

Our class that networks with this backend API using URLSession may look something like this —

We have defined a method ‘getProfile()’ in our class which networks with the backend API and returns the user. For this simple example, we simply print the name of the user received (line 19).

We can use this class to get the user simply like this —

Code
let profileAPI = ProfileAPI()
profileAPI.getProfile()
Sample Output
Yugantar

2. Make REST API Networking Class Testable

Our ProfileAPI class isn’t testable and ready for mocking yet.

To be able to mock it, we’ll have to make some changes in it after which the final class may look something like this —

We have done two main changes to make our ProfileAPI class testable.

Introduce a property ‘session’ of type URLSession —

Now, instead of always using URLSession.shared for networking, we’ll use the ‘session’ property instead (line 19).

By default, ‘session’ will be equal to URLSession.shared (init method), hence, eliminating the need to unnecessarily specify it every time or update existing code.

However, it can also be injected with a different URLSession type whenever required (eg. a custom mock url session for unit testing).

Declare a completion handler and use that in the sink operator —

On line 18, we declare a completion handler in the getProfile() method as a function parameter. This completion handler is then used inside the sink operator, replacing the print statement.

The completion handler has been implemented as an escaping closure; this is essential since URLSession and networking in general is highly asynchronous.

Using a completion handler instead of directly defining our logic inside the API class not only helps in modularity, but also to make it testable. Since now, assert statements can be used inside the completion handler in our unit tests.

Now, using the class to get the user would look like this —

Code
let profileAPI = ProfileAPI()
profileAPI.getProfile { user in
print(user.name)
}
Sample Output
Yugantar

3. Subclass URLProtocol for mocking, and unit test our API class

Usually, to implement mocking and test networking code, mock classes are built for each API class that do not directly communicate with server.

This usually leads to a lot of duplication in code; and as you can imagine, this easily becomes a cumbersome task with growing number of API classes.

Instead, Apple recommends subclassing URLProtocol to build a MockURLProtocol which can be used for every API class.

Source: Testing Tips & Tricks, WWDC18 Apple Developer Session

The MockURLProtocol that we develop on subclassing URLProtocol is boilerplate code, so let’s just add this code to our test case file —

We’re all set now! Let’s unit test our networking code —

We use the setUpWithError() method to set our custom url session for mocking (this is boilerplate too, can be copied as is) and assign it.

In our test method (testGetProfile), we declare an object for our ProfileAPI class and inject our custom url session in it.

After that, we set the custom data that we expect to receive on a successful request and tell the MockURLProtocol to return that (the data we get in the sink operator).

Our completion handler gets this data (in sink, we do completion(user)).

We use an XCTestExpectation (used to test asynchronous code), and fulfill it in the closure after our Assert statements (for testing). Lastly, we wait a little for this expectation. And we’re done!

Reference:

Testing Tips & Tricks, WWDC18 Apple Developer Session

Thank You for Reading!

Please feel free to reach out to me and connect, check out our projects, or join our open source community:
LinkedIn, GitHub, Mentorship iOS, AnitaB.org Community

--

--