Mocking Network Calls in Swift
Yes, you can test your code without making genuine network calls

When testing there are various reasons why you might not want to test against the real version of an API. This might be because you want to make sure that you are only testing one class or method of your code at once (so you can separate out different pieces of your testing regime), or (as in this case) you don’t want to make real network calls (because they might be flaky or you might not guarantee that the network connection is available in your testing environment.
Difficulty: Beginner | Easy | Normal | Challenging
Terminology
API: Application programming interface. A set of accessible tools for building software applications
Class: An object that defines properties and methods in common
Data Task: A task that retrieves the contents of a URL
Method: A function that defines the behaviour of object
Mocking: A fake response to the method call for of an object, allowing the checking of a particular method call or property
Result Type: A value that represents either a success or a failure, including an associated value in each case
The use of mocking
To be able to test network calls without actually hitting the remote server through thoseAPI
calls is an invaluable skill to learn.
Look, putting load on a server just to test might seem like a silly thing to do because of the straight cost of doing so but it’s more than that. Waiting for a response from a remote API
is slowwwwww and you are dependent on that API for your tests passing.
Step by Step Code allowing Testability
Downloading from a URL
In this case, we are downloading a 10MB file from http://ipv4.download.thinkbroadband.com/10MB.zip although any file URL
you can think of would operate in the same way.
Theory: A data task to download
To download a file data task
is a method on the URLSession
class
that allows us the download the contents of a URL
.
Create a simple function to download data
This gives us a simple function, that accepts a parameter that is a URLSession
(for easy use of dependency injection later).
Now I do sometimes forget that we need to task.resume()
to start the download, but that’s my only complaint (and as such I’m criticising my own understanding).
Now to run this method
we run the following:
downloadData(URLSession.shared)
To get the code to download (even in the simulator) you will need to adjust the App Transport Security Security settings (guide HERE).
Putting it together we can add the following to a view controller
It should be noted that this type of architecture violates the single responsibility principle and of course there are better ways to structure the architecture of your App. However this article is about testing.
Simple testing
We set the storyboardid for the view controller in the storyboard (there is a guide for initialising a view controller in Swift Guide), so the viewcontroller can be intialised.
It seems like there is no problem here. Now we can run our function in the viewcontroller.
You know what happens here. It just isn’t reliable even with the 10 seconds timeout — I can get a response around 1 in 3 times (that gives a passing test).
There must be a better way. Luckily, of course there is!
We shouldn’t be making the network call from this test at all. Even if the test above worked, it has to make the call each time, and this takes too long.
This code is contained within the repo towards the bottom of the page, and within that repo this code is in a NaiveMockNetworkCalls folder.
Mocking URLSession through different methods
We want to test our code without actually downloading anything, we need to set up a mock
for URLSession
.
Subclassing URLSession
Our first attempt at this mock
class has two sections, that is URLSessionDataTaskMock and URLSessionMock. The URLSession itself is subclassed, and this means we can override the dataTask functionality.
The URLSessionDataTaskMock returns a closure it has been given when when resumed; that is when it is called it will simply return that closure.
The URLSessionMock is a class that returns that URLSessionDataTaskMock rather than the usual URLSessionDataTask (which itself is usually expected to return the downloaded data into the App’s memory).
As a result, when then call this with the following test, we inject some data (so there is something for URLSessionDataTaskMock to be able to return. The URLSessionMock() can be injected into the function downloadData as URLSessionMock() is actually a URLSession
so no casting is required.
Now when the test is run, we don’t have to wait for the network to return — obviously it isn’t quite instant but on my machine I could change the timeout to 0.1 within testUsingSimpleMock.
This code is contained within the repo towards the bottom of the page, and within that repo this code is in a NaiveMockNetworkCalls folder.
The problem with subclassing
Subclassing URLSession
and DataTask
means that we are exposed to any changes that Apple might make to these classes
, and (as you have seen) there is quite alot of code that is required for just some mocking
.
Failed Attempt 1: Mock URLSession, conforming to a protocol and Mock DataTaskMock
Our second attempt at mocking
here requires that we conform to a protocol.
protocol URLSessionProtocol {
func dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
}
So we need to extend URLSession
to conform to the protocol
extension URLSession: URLSessionProtocol {}
Then we create the URLSession
as before, however we need to mock
the data task
too where it conforms to the following URLSessionDataTaskProtocol
protocol URLSessionDataTaskProtocol {
func resume()
}
so URLSessionDataTaskMock is as follows:
Now the issue is that URLSessionDataTaskMock
within the URLSessionProtocol
doesn’t have the correct type - It needs to return a URLSessionDataTask
and it can’t be cast as above — that is it always fails.
Method 2: Mock URLSession, subclass DataTask
One (working) solution is to mock URLSession
but to subclass DataTask
. This allows us to create a skinny DataTaskMock which basically overrides resume to with an empty function body.
This code is contained within the repo towards the bottom of the page, and within that repo this code is in a MockNetworkCalls folder.
Method 3: Mock URLSession, Mock DataTask
We can mock both URLSession
and DataTask
by using similar techniques to the one above.
Now we change the URLSessionProtocol to return an associated type
protocol URLSessionProtocol {
associatedtype dta: URLSessionDataTaskProtocolfunc dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> dta
}
where URLSessionDataTaskProtocol is as before
protocol URLSessionDataTaskProtocol {
func resume()
}
and the extensions are written the same as before above.
We have to use a generic constraint in our function signature as below
Our mock class is then represented by the following:
which is then tested with exactly the same test as before (full code is in the repo at the link just under this code):
class MockNetworkCallsTests: XCTestCase {
func testUsingSimpleMock() {
let mockSession = URLSessionMock()
mockSession.data = "testData".data(using: .ascii)
let exp = expectation(description: "Loading URL")
let vc = ViewController()
vc.downloadData(mockSession, completionBlock: {data in
exp.fulfill()
})
waitForExpectations(timeout: 0.1)
}
}
This code is contained within the repo towards the bottom of the page, and within that repo this code is in a FullMockNetworkCalls folder.
Repo
This is a rather long article, so please download the repo to find the code
Conclusion:
Mocking
prevents your tests committing the following sins:
- Waiting for a slow API call
- Hitting your remote API (at cost to the business)
- Dependent on an external resource for your tests to pass
Networking being one essential use for mocking, I hope this guide has helped you out a little!
The Twitter contact:
Any questions? You can get in touch with me HERE