Как правило, некоторая форма внедрения зависимости. Будь то внутренне представленная переменная для DispatchQueue, аргумент по умолчанию в функции с очередью или аргумент конструктора. Вам просто нужен какой-то способ пройти тестовую очередь, которая отправляет событие, когда вам нужно.
DispatchQueue.main.async
запланирует асинхронную блокировку вызываемого абонента в главной очереди и, следовательно, не будет гарантировано к тому времени, когда вы сделаете утверждение.
Пример (заявление об отказе: я печатаю по памяти, поэтому он может не скомпилироваться, но дает идею):
// In test code.
struct TestQueue: DispatchQueue {
// make sure to impement other necessary protocol methods
func async(block: () -> Void) {
// you can even have some different behavior for when to execute the block.
// also you can pass XCTestExpectations to this TestQueue to be fulfilled if necessary.
block()
}
}
// In source code. In test, pass the Test Queue to the first argument
func doSomething(queue: DispatchQueue = DispatchQueue.main, completion: () -> Void) {
queue.async(block: completion)
}
Другие методы тестирования асинхронности и устранения условий гонки связаны с искусным выполнением XCTestExpectation.
Если у вас есть доступ к блоку завершения, который в конечном итоге вызывается:
// In source
class Subject {
func doSomethingAsync(completion: () -> Void) {
...
}
}
// In test
func testDoSomethingAsync() {
let subject = Subject()
let expect = expectation(description: "does something asnyc")
subject.doSomethingAsync {
expect.fulfill()
}
wait(for: [expect], timeout: 1.0)
// assert something here
// or the wait may be good enough as it will fail if not fulfilled
}
Если у вас нет доступа к блоку завершения, это обычно означает, что нужно найти способ внедрить или создать подкласс тестового двойника, для которого вы можете установить XCTestExpectation, и в конечном итоге он выполнит ожидание после завершения асинхронной работы.