DispatchQueue.concurrentPerform in XCUnitTest

I've been provided code that takes a closure. I want to get an idea of the robustness of the provided code, so I've been building a bunch of unittests.

So far, so good. Next step I want to test for concurrency. I've got the following code, which can be run by creating an empty project that includes unittests, dropping a file named "kodim01.png" into the unittest folder of the project, and replace the example with the following code.

func testExample() {
    let url = Bundle(for: type(of: self )).url(forResource: "kodim01", withExtension: "png")
    let request = URLRequest(url: url!)
    let max = 10
    let expectations = (0 ..< max).map { self.expectation(description: String(describing: $0)) }

    DispatchQueue.concurrentPerform(iterations: max) { i in
        URLSession.shared.dataTask(with: request) { _, _, _ in
            expectations[i].fulfill()
        }
    }
    
    wait(for: expectations, timeout: 3)
}

However this completely not works. The console says: Asynchronous wait failed: Exceeded timeout of 3 seconds, with unfulfilled expectations: "0", "1", "2", "3", "4", "5", "6", "7", "8", "9".

Why?

Are you sure you're using dataTask correctly? The documentation has this to say:

After you create the task, you must start it by calling its resume method.

I don't see a call to resume anywhere. I think this is what you want:

let task = URLSession.shared.dataTask(with: request) { ... }
task.resume()

You create the task, then tell it to start.


On another note, dataTask produces a task that always performs its request in the background. There's no reason to use concurrentPerform here. The data task will run concurrently in the background regardless of whether you're using concurrentPerform or not, and then call the completion handler once its done.

1 Like

On first glance this looks like the old non async API of URLSession, i.e. you create a task that you actively have to start by calling resume() on it, no? So like:

DispatchQueue.concurrentPerform(iterations: max) { i in
    let dataTask = URLSession.shared.dataTask(with: request) { _, _, _ in
        expectations[i].fulfill()
    }
    dataTask.resume()
}

Ah, too late. :slight_smile:
Also, I am surprised you don't get a warning for ignoring the return value, is dataTask(with:) marked as @discardableResult?

1 Like

@michelf @Gero
Thanks so much for your observations. I've been staring at this wayyyyy too long.