Search code examples
iosswiftunit-testingnsurlsessionxctestexpectation

How to write unit test for `downloadTask` with `completionHandler `?


Here is my downloading function:

// Download a file from the url to the local directory
class func downloadUrl(url: URL, to dirUrl: URL, completion: (() -> ())?){
    let sessionConfig = URLSessionConfiguration.default
    let session = URLSession(configuration: sessionConfig)
    let request = URLRequest(url: url)

    let task = session.downloadTask(with: request) {(tempLocalUrl, response, error) in
        if let tempLocalUrl = tempLocalUrl, error == nil {
            // Success, copy the downloaded file from the memory to the disk
            print("Finished downloading!")
            do {
                try FileManager.default.copyItem(at: tempLocalUrl,to:
                    dirUrl.appendingPathComponent((response?.suggestedFilename)!))
                if completion != nil {
                    completion!()
                }
            } catch (let writeError) {
                print("Fliled to write file \(dirUrl) : \(writeError)")
            }
        } else {
            print("Failure: \(String(describing: error?.localizedDescription))")
        }
    }
    task.resume()
}

I want to write a unit test method to test whether it downloads the file from url to the dirUrl.

func testDownloadUrl(){
    let fm = FileManager.default
    let url = URL(string: "https://raw.githubusercontent.com/apple/swift/master/README.md")
    fileDownloader.downloadUrl(url: url!, to: fm.temporaryDirectory, completion: nil)

    // Check the contents of temp file
    let tempContents = try? fm.contentsOfDirectory(atPath: fm.temporaryDirectory.path)
        print("Contents: \(tempContents)")
    }

However, there was no output "Finished downloading!" or "Failure ..." even though I passed the unit test, so I guess the completionHandler was not called in this test case.

My question is how to make the unit test method wait until the download task completes?


Solution

  • The standard idiom is to use Xcode asynchronous testing APIs, mostly the XCTestExpectation class. Those are available since Xcode 6 so, if you are using a recent version, you should be covered ;)

    The general async test template is as follows:

    func testAsyncFunction() {
        let asyncDone = expectation(description: "Async function")
        ...
        someAsyncFunction(...) {
            ...
            asyncDone.fulfill()
        }
        wait(for: [asyncDone], timeout: 10)
        /* Test the results here */
    }
    

    This will block your test execution until the aforementioned function completes (or the specified timeout has elapsed).