Mocking network calls in Swift
By: Steven Curtis
Tags: Mac, Software Engineering, Swift, Programming, Software Development

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: URLSessionDataTaskProtocol func 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.