One way of testing code with DispatchQueue is by using expectations (https://developer.apple.com/documentation/xctest/asynchronous_tests_and_expectations/testing_asynchronous_operations_with_expectations) but in most of the cases it’s more clean to treat the DispatchQueue as a dependency. In those cases as almost always passing the dependency through the constructor is the ideal way. This gives us the option to make clear to a reader that this object is doing some work on a queue and also enable us to use the object with a different queue if needed.
Let’s see a typical example with a ViewController:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import UIKit class DispatchQueueOnViewController: UIViewController { @IBOutlet weak var someDataLabel: UILabel! init() { super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @IBAction func didTapButton(_ sender: Any) { DispatchQueue.main.async { self.someDataLabel.text = "something loaded" } } } |
Using dependency injection we can write the above code as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import UIKit class DispatchQueueOnViewController: UIViewController { @IBOutlet weak var someDataLabel: UILabel! private let dispatchQueue: DispatchQueueType init(dispatchQueue: DispatchQueueType) { self.dispatchQueue = dispatchQueue super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @IBAction func didTapButton(_ sender: Any) { dispatchQueue.async { self.someDataLabel.text = "something loaded" } } } protocol DispatchQueueType { func async(execute work: @escaping @convention(block) () -> Void) } extension DispatchQueue: DispatchQueueType { func async(execute work: @escaping @convention(block) () -> Void) { async(group: nil, qos: .unspecified, flags: [], execute: work) } } |
Now in order to unit testing now the didTapButton action all we have to do is to initialize the ViewController with a test double for the DispatchQueue.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import XCTest @testable import UnitTestDispatchQueue class DispatchQueueOnViewControllerTests: XCTestCase { func test_DidTapButton_setsValueOnLabel() { let fakeQueue = DispatchQueueFake() let sut = DispatchQueueOnViewController(dispatchQueue: fakeQueue) _ = sut.view sut.didTapButton(UIButton()) XCTAssertEqual("something loaded" , sut.someDataLabel.text) } } final class DispatchQueueFake: DispatchQueueType { func async(execute work: @escaping @convention(block) () -> Void) { work() } } |