Mocking Network Calls in Swift

Yes, you can test your code without making genuine network calls

Prerequisites:

  • Understand the difference between the terms subbing, mocking and faking (Guide HERE)
  • We will be using Swift’s result type (guide HERE) within this post
  • Be able to allow Arbitrary loads in your Plist settings (guide HERE)

Terminology

API: Application programming interface. A set of accessible tools for building software applications

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.

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).

downloadData(URLSession.shared)

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.

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 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
}
extension URLSession: URLSessionProtocol {}
protocol URLSessionDataTaskProtocol {
func resume()
}

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.

Method 3: Mock URLSession, Mock DataTask

We can mock both URLSession and DataTask by using similar techniques to the one above.

protocol URLSessionProtocol {
associatedtype dta: URLSessionDataTaskProtocol
func dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> dta
}
protocol URLSessionDataTaskProtocol {
func resume()
}
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)
}
}

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:

  • Hitting your remote API (at cost to the business)
  • Dependent on an external resource for your tests to pass

The Twitter contact:

Any questions? You can get in touch with me HERE

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store