2
\$\begingroup\$

I want to make testable network calls using Swift.

For example, I want to download the latest from AWS if and only if it's version is newer than the downloaded file.

In order to do this I've created a function:

func requestDownloadLinkForAWS() {
    DispatchQueue.global(qos: .background).async { [weak self] in
        guard let self = self else {return}
        // Hard coded download for testing - where will this compatibility file be hosted??
        do {
            let decoder = JSONDecoder()
                if let dataResponse = content.data?[0]
                    {
                        if (dataResponse.appMinimumVersion >= appVersionString) && (dataResponse.appTargetVersion >= appVersionString) {
                            if let version = self.stagingBundle.getVersion()
                            {
                                if (version < dataResponse.version) {
                                    self.downloadLatestFileAWS(dataResponse.bucketName, dataResponse.folderName, dataResponse.version)
                                }
                            }
                        }
                    }
            }
        } catch let error as NSError {
            print ("\(error) 2 error")
        }
    }
}

Now my self.downloadLatestFileAWS is an async function that I can easily test.

My problem is to test the function above. There is no output here, but I need to test this function. What refactoring could I do to make this testable?

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

You can refactor this method (and downloadLatestFileAWS, too), to take an optional completion handler parameter:

func requestDownloadLinkForAWS(completion: @escaping ((Result<URL, Error>) -> Void)? = nil) {
    ...

    do {
        ...
        downloadLatestFileAWS(dataResponse.bucketName, dataResponse.folderName, dataResponse.version, completion: completion)
    } catch let error {
        completion?(.failure(error))
    }
}

func downloadLatestFileAWS(_ bucket: String, _ folder: String, _ version, completion: @escaping ((Result<URL, Error>) -> Void)? = nil) {
    ...
    completion?(.success(url)) // the file URL of the saved file
}

Then you can test it:

func testDownload() {
    var result: String?

    let expectation = self.expectation(description: "download")
    requestDownloadLinkForAWS { result in
        if case .failure(_) = result {
            XCTFail("Download failed")
        }
        expectation.fulfill()
    }
    wait(for: [expectation], timeout: 10)
}

By the way, by adding a completion handler to the download method, it also offers you the opportunity to have the caller specify what UI updates should take place when the download is done. For example, you generally don’t want to bury those UI updates within downloadLatestFileAWS. Also, rather than just printing the error to the Xcode console, the caller can now specify what it wants to do when an error occurs.

That having been said, you might not want to actually perform network requests in unit tests. You might, instead mock the network service, or use URLProtocol to mock it behind the scenes.

But either way, you can make your methods testable by providing them with completion handler closures.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.