How to test a method that contains Task Async/await in swift - swift

Given the following method that contains a Task.
self.interactor is mocked.
func submitButtonPressed() {
Task {
await self.interactor?.fetchSections()
}
}
How can I write a test to verify that the fetchSections() was called from that method?!
My first thought was to use expectations and wait until it is fulfilled (in mock's code).
But is there any better way with the new async/await?

Ideally, as you imply, your interactor would be declared using a protocol so that you can substitute a mock for test purposes. You then consult the mock object to confirm that the desired method was called. In this way you properly confine the scope of the system under test to answer only the question "was this method called?"
As for the structure of the test method itself, yes, this is still asynchronous code and, as such, requires asynchronous testing. So using an expectation and waiting for it is correct. The fact that your app uses async/await to express asynchronousness does not magically change that! (You can decrease the verbosity of this by writing a utility method that creates a BOOL predicate expectation and waits for it.)

I don't know if you already find a solution to your question, but here is my contribution to other developers facing the same problem.
I was in the same situation as you, and I solved the problem by using Combine to notify the tested class that the method was called.
Let's say that we have this method to test:
func submitButtonPressed() {
Task {
await self.interactor?.fetchSections()
}
}
We should start by mocking the interaction:
import Combine
final class MockedInteractor: ObservableObject, SomeInteractorProtocol {
#Published private(set) var fetchSectionsIsCalled = false
func fetchSection async {
fetchSectionsIsCalled = true
// Do some other mocking if needed
}
}
Now that we have our mocked interactor we can start write unit test:
import XCTest
import Combine
#testable import YOUR_TARGET
class MyClassTest: XCTestCase {
var mockedInteractor: MockedInteractor!
var myClass: MyClass!
private var cancellable = Set<AnyCancellable>()
override func setUpWithError() throws {
mockedInteractor = .init()
// the interactor should be injected
myClass = .init(interactor: mockedInteractor)
}
override func tearDownWithError() throws {
mockedInteractor = nil
myClass = nil
}
func test_submitButtonPressed_should_callFetchSections_when_Always(){
//arrage
let methodCallExpectation = XCTestExpectation()
interactor.$fetchSectionsIsCalled
.sink { isCalled in
if isCalled {
methodCallExpectation.fulfill()
}
}
.store(in: &cancellable)
//acte
myClass.submitButtonPressed()
wait(for: [methodCallExpectation], timeout: 1)
//assert
XCTAssertTrue(interactor.fetchSectionsIsCalled)
}

There was one solution suggested here (#andy) involving injecting the Task. There's a way to do this by the func performing the task returning the Task and allows a test to await the value.
(I'm not crazy about changing a testable class to suit the test (returning the Task), but it allows to test async without NSPredicate or setting some arbitrary expectation time (which just smells)).
#discardableResult
func submitButtonPressed() -> Task<Void, Error> {
Task { // I'm allowed to omit the return here, but it's returning the Task
await self.interactor?.fetchSections()
}
}
// Test
func testSubmitButtonPressed() async throws {
let interactor = MockInteractor()
let task = manager.submitButtonPressed()
try await task.value
XCTAssertEqual(interactor.sections.count, 4)
}

I answered a similar question in this post: https://stackoverflow.com/a/73091753/2077405
Basically, given code defined like this:
class Owner{
let dataManager: DataManagerProtocol = DataManager()
var data: String? = nil
init(dataManager: DataManagerProtocol = DataManager()) {
self.dataManager = dataManager
}
func refresh() {
Task {
self.data = await dataManager.fetchData()
}
}
}
and the DataManagerProtocol is defined as:
protocol DataManagerProtocol {
func fetchData() async -> String
}
a mock/fake implementation can be defined:
class MockDataManager: DataManagerProtocol {
func fetchData() async -> String {
"testData"
}
}
Implementing the unit test should go like this:
...
func testRefreshFunctionFetchesDataAndPopulatesFields() {
let expectation = XCTestExpectation(
description: "Owner fetches data and updates properties."
)
let owner = Owner(mockDataManager: DataManagerProtocol())
// Verify initial state
XCTAssertNil(owner.data)
owner.refresh()
let asyncWaitDuration = 0.5 // <= could be even less than 0.5 seconds even
DispatchQueue.main.asyncAfter(deadline: .now() + asyncWaitDuration) {
// Verify state after
XCTAssertEqual(owner.data, "testData")
expectation.fulfill()
}
wait(for: [expectation], timeout: asyncWaitDuration)
}
...
Hope this makes sense?

Related

Can I call an actor's function from a #Sendable sync function?

I'm not sure if this is even possible, but I thought I should ask anyway!
I have a "UserDefaults" client and "live"/"mock" implementations of its interface (taken from a TCA example, but that shouldn't matter).
I want to have the mock implementation act like the live one (so that it should be possible to read/write values), but without actually persisting.
Here's my code (ready for a Playground):
import Foundation
import PlaygroundSupport
// Interface - this can't change
public struct UserDefaultsClient {
public var boolForKey: #Sendable (String) -> Bool
public var setBool: #Sendable (Bool, String) async -> Void
}
// Live implementation (ignore if you want, just for reference)
extension UserDefaultsClient {
public static let live: Self = {
let defaults = { UserDefaults(suiteName: "group.testing")! }
return Self(
boolForKey: { defaults().bool(forKey: $0) },
setBool: { defaults().set($0, forKey: $1) }
)
}()
}
// Mock implementation
extension UserDefaultsClient {
static let mock: Self = {
let storage = Storage()
return Self(
boolForKey: { key in
// ❌ Compiler error: Cannot pass function of type '#Sendable (String) async -> Bool' to parameter expecting synchronous function type
return await storage.getBool(for: key)
// return false
},
setBool: { boolValue, key in
await storage.setBool(boolValue, for: key)
}
)
}()
}
// Manages a storage of Bools, mapped to a key
actor Storage {
var storage: [String: Bool] = [:]
func setBool(_ bool: Bool, for key: String) async {
storage[key] = bool
}
func getBool(for key: String) -> Bool {
storage[key] ?? false
}
}
let client: UserDefaultsClient = .live
Task {
client.boolForKey("key")
await client.setBool(true, "key")
client.boolForKey("key")
}
PlaygroundPage.current.needsIndefiniteExecution = true
I tried using a Task when calling storage.getBool, but that didn't work either.
Any ideas?
tl;dr the answer is No, there is no way to maintain both the interface that boolForKey is synchronous but have it perform an asynchronous operation - while maintaining the model that makes async/await valuable.
You are asking how to run an asynchronous task synchronously. It's kind of like asking how to make dry water. You are going against the fundamental nature of the asyc/await paradigm and its primitives.
You might be able to accomplish what you with synchronization primitives like DispatchSemaphore or something similar, but you'd be working around the system. You'd have no guarantees about future compatibility. And you would likely introduce subtle and pernicious bugs.
Consider, for example, if some call path in your actor ever managed to come back around and call boolForKey. That would probably be a deadlock.

Swift Concurrency async/await equivalent of a dispatch barrier / semaphore [duplicate]

I've a document based application that uses a struct for its main data/model. As the model is a property of (a subclass of) NSDocument it needs to be accessed from the main thread. So far all good.
But some operations on the data can take quite a long time and I want to provide the user with a progress bar. And this is where to problems start. Especially when the user starts two operations from the GUI in quick succession.
If I run the operation on the model synchronously (or in a 'normal' Task {}) I get the correct serial behaviour, but the Main thread is blocked, hence I can't show a progress bar. (Option A)
If I run the operation on the model in a Task.detached {} closure I can update the progress bar, but depending on the run time of the operations on the model, the second action of the user might complete before the first operation, resulting in invalid/unexpected state of the model. This is due to the await statements needed in the detached task (I think). (Option B).
So I want a) to free up the main thread to update the GUI and b) make sure each task runs to full completion before another (queued) task starts. This would be quite possible using a background serial dispatch queue, but I'm trying to switch to the new Swift concurrency system, which is also used to perform any preparations before the model is accessed.
I tried using a global actor, as that seems to be some sort of serial background queue, but it also needs await statements. Although the likelihood of unexpected state in the model is reduced, it's still possible.
I've written some small code to demonstrate the problem:
The model:
struct Model {
var doneA = false
var doneB = false
mutating func updateA() {
Thread.sleep(forTimeInterval: 5)
doneA = true
}
mutating func updateB() {
Thread.sleep(forTimeInterval: 1)
doneB = true
}
}
And the document (leaving out standard NSDocument overrides):
#globalActor
struct ModelActor {
actor ActorType { }
static let shared: ActorType = ActorType()
}
class Document: NSDocument {
var model = Model() {
didSet {
Swift.print(model)
}
}
func update(model: Model) {
self.model = model
}
#ModelActor
func updateModel(with operation: (Model) -> Model) async {
var model = await self.model
model = operation(model)
await update(model: model)
}
#IBAction func operationA(_ sender: Any?) {
//Option A
// Task {
// Swift.print("Performing some A work...")
// self.model.updateA()
// }
//Option B
// Task.detached {
// Swift.print("Performing some A work...")
// var model = await self.model
// model.updateA()
// await self.update(model: model)
// }
//Option C
Task.detached {
Swift.print("Performing some A work...")
await self.updateModel { model in
var model = model
model.updateA()
return model
}
}
}
#IBAction func operationB(_ sender: Any?) {
//Option A
// Task {
// Swift.print("Performing some B work...")
// self.model.updateB()
// }
//Option B
// Task.detached {
// Swift.print("Performing some B work...")
// var model = await self.model
// model.updateB()
// await self.update(model: model)
// }
//Option C
Task.detached {
Swift.print("Performing some B work...")
await self.updateModel { model in
var model = model
model.updateB()
return model
}
}
}
}
Clicking 'Operation A' and then 'Operation B' should result in a model with two true's. But it doesn't always.
Is there a way to make sure that operation A completes before I get to operation B and have the Main thread available for GUI updates?
EDIT
Based on Rob's answer I came up with the following. I modified it this way because I can then wait on the created operation and report any error to the original caller. I thought it easier to comprehend what's happening by including all code inside a single update function, so I choose to go for a detached task instead of an actor. I also return the intermediate model from the task, as otherwise an old model might be used.
class Document {
func updateModel(operation: #escaping (Model) throws -> Model) async throws {
//Update the model in the background
let modelTask = Task.detached { [previousTask, model] () throws -> Model in
var model = model
//Check whether we're cancelled
try Task.checkCancellation()
//Check whether we need to wait on earlier task(s)
if let previousTask = previousTask {
//If the preceding task succeeds we use its model
do {
model = try await previousTask.value
} catch {
throw CancellationError()
}
}
return try operation(model)
}
previousTask = modelTask
defer { previousTask = nil } //Make sure a later task can always start if we throw
//Wait for the operation to finish and store the model
do {
self.model = try await modelTask.value
} catch {
if error is CancellationError { return }
else { throw error }
}
}
}
Call side:
#IBAction func operationA(_ sender: Any?) {
//Option D
Task {
do {
try await updateModel { model in
var model = model
model.updateA()
return model
}
} catch {
presentError(error)
}
}
}
It seems to do anything I need, which is queue'ing updates to a property on a document, which can be awaited for and have errors returned, much like if everything happened on the main thread.
The only drawback seems to be that on the call side the closure is very verbose due to the need to make the model a var and return it explicitly.
Obviously if your tasks do not have any await or other suspension points, you would just use an actor, and not make the method async, and it automatically will perform them sequentially.
But, when dealing with asynchronous actor methods, one must appreciate that actors are reentrant (see SE-0306: Actors - Actor Reentrancy). If you really are trying to a series of asynchronous tasks run serially, you will want to manually have each subsequent task await the prior one. E.g.,
actor Foo {
private var previousTask: Task<(), Error>?
func add(block: #Sendable #escaping () async throws -> Void) {
previousTask = Task { [previousTask] in
let _ = await previousTask?.result
return try await block()
}
}
}
There are two subtle aspects to the above:
I use the capture list of [previousTask] to make sure to get a copy of the prior task.
I perform await previousTask?.value inside the new task, not before it.
If you await prior to creating the new task, you have race, where if you launch three tasks, both the second and the third will await the first task, i.e. the third task is not awaiting the second one.
And, perhaps needless to say, because this is within an actor, it avoids the need for detached task, while keeping the main thread free.

Unit testing a class that depends on an async function

I have a view model with a state property enum that has 3 cases.
protocol ServiceType {
func doSomething() async
}
#MainActor
final class ViewModel {
enum State {
case notLoaded
case loading
case loaded
}
private let service: ServiceType
var state: State = .notLoaded
init(service: ServiceType) {
self.service = service
}
func load() async {
state = .loading
await service.doSomething()
state = .loaded
}
}
I want to write a unit test that asserts that after load is called but before the async function returns, state == .loading .
If I was using completion handlers, I could create a spy that implements ServiceType, captures that completion handler but doesn't call it. If I was using combine I could use a schedular to control execution.
Is there an equivalent solution when using Swift's new concurrency model?
As you're injecting the depencency via a protocol, you're in a very good position for providing a Fake for that protocol, a fake which you have full control from the unit tests:
class ServiceFake: ServiceType {
var doSomethingReply: (CheckedContinuation<Void, Error>) -> Void = { _ in }
func doSomething() async {
// this creates a continuation, but does nothing with it
// as it waits for the owners to instruct how to proceed
await withCheckedContinuation { doSomethingReply($0) }
}
}
With the above in place, your unit tests are in full control: they know when/if doSomething was called, and can instruct how the function should respond.
final class ViewModelTests: XCTestCase {
func test_viewModelIsLoadingWhileDoSomethingSuspends() {
let serviceFake = ServiceFake()
let viewModel = ViewModel(service: serviceFake)
XCTAssertEquals(viewModel.state, .notLoaded)
let expectation = XCTestExpectation(description: "doSomething() was called")
// just fulfilling the expectation, because we ignore the continuation
// the execution of `load()` will not pass the `doSomething()` call
serviceFake.doSomethingReply = { _ in
expectation.fulfill()
}
Task {
viewModel.load()
}
wait(for: [expectation], timeout: 0.1)
XCTAssertEqual(viewModel.state, .loading)
}
}
The above test makes sure doSomething() is called, as you likely don't want to validate the view model state until you're sure the execution of load() reached the expected place - afterall, load() is called on a different thread, so we need an expectation to make sure the test properly waits until the thread execution reaches the expected point.
The above technique is very similar to a mock/stub, where the implementation is replaced with a unit-test provided one. You could even go further, and just have an async closure instead of a continuation-based one:
class ServiceFake: ServiceType {
var doSomethingReply: () async -> Void = { }
func doSomething() async {
doSomethingReply()
}
}
, and while this would give even greater control in the unit tests, it also pushes the burden of creating the continuations on those unit tests.
You can handle this the similar way you were handling for completion handler, you have the choice to either delay the completion of doSomething using Task.sleep(nanoseconds:) or you can use continuation to block the execution forever by not resuming it same as you are doing with completion handler.
So your mock ServiceType for the delay test scenario looks like:
struct HangingSevice: ServiceType {
func doSomething() async {
let seconds: UInt64 = 1 // Delay by seconds
try? await Task.sleep(nanoseconds: seconds * 1_000_000_000)
}
}
Or for the forever suspended scenario:
class HangingSevice: ServiceType {
private var continuation: CheckedContinuation<Void, Never>?
deinit {
continuation?.resume()
}
func doSomething() async {
let seconds: UInt64 = 1 // Delay by seconds
await withCheckedContinuation { continuation in
self.continuation?.resume()
self.continuation = continuation
}
}
}

Swift 5.5 test async Task in init

I would like to test if my init function works as expected. There is an async call in the init within a Task {} block. How can I make my test wait for the result of the Task block?
class ViewModel: ObservableObject {
#Published private(set) var result: [Item]
init(fetching: RemoteFetching) {
self.result = []
Task {
do {
let result = try await fetching.fetch()
self.result = result // <- need to do something with #MainActor?
} catch {
print(error)
}
}
}
}
Test:
func testFetching() async {
let items = [Item(), Item()]
let fakeFetching = FakeFetching(returnValue: items)
let vm = ViewModel(fetching: FakeFetching())
XCTAssertEqual(vm.result, [])
// wait for fetching, but how?
XCTAssertEqual(vm.result, items])
}
I tried this, but setting the items, only happens after the XCTWaiter. The compiler warns that XCTWaiter cannot be called with await, because it isn't async.
func testFetching() async {
let items = [Item(), Item()]
let fakeFetching = FakeFetching(returnValue: items)
let expectation = XCTestExpectation()
let vm = ViewModel(fetching: FakeFetching())
XCTAssertEqual(vm.result, [])
vm.$items
.dropFirst()
.sink { value in
XCTAssertEqual(value, items)
expectation.fulfill()
}
.store(in: &cancellables)
let result = await XCTWaiter.wait(for: [expectation], timeout: 1)
XCTAssertEqual(result, .completed)
}
Expectation-and-wait is correct. You're just using it wrong.
You are way overthinking this. You don't need an async test method. You don't need to call fulfill yourself. You don't need a Combine chain. Simply use a predicate expectation to wait until vm.result is set.
Basically the rule is this: Testing an async method requires an async test method. But testing the asynchronous "result" of a method that happens to make an asynchronous call, like your init method, simply requires good old-fashioned expectation-and-wait test.
I'll give an example. Here's a reduced version of your code; the structure is essentially the same as what you're doing:
protocol Fetching {
func fetch() async -> String
}
class MyClass {
var result = ""
init(fetcher: Fetching) {
Task {
self.result = await fetcher.fetch()
}
}
}
Okay then, here's how to test it:
final class MockFetcher: Fetching {
func fetch() async -> String { "howdy" }
}
final class MyLibraryTests: XCTestCase {
let fetcher = MockFetcher()
func testMyClassInit() {
let subject = MyClass(fetcher: fetcher)
let expectation = XCTNSPredicateExpectation(
predicate: NSPredicate(block: { _, _ in
subject.result == "howdy"
}), object: nil
)
wait(for: [expectation], timeout: 2)
}
}
Extra for experts: A Bool predicate expectation is such a common thing to use, that it will be found useful to have on hand a convenience method that combines the expectation, the predicate, and the wait into a single package:
extension XCTestCase {
func wait(
_ condition: #escaping #autoclosure () -> (Bool),
timeout: TimeInterval = 10)
{
wait(for: [XCTNSPredicateExpectation(
predicate: NSPredicate(block: { _, _ in condition() }), object: nil
)], timeout: timeout)
}
}
The outcome is that, for example, the above test code can be reduced to this:
func testMyClassInit() {
let subject = MyClass(fetcher: fetcher)
wait(subject.result == "howdy")
}
Convenient indeed. In my own code, I often add an explicit assert, even when it is completely redundant, just to make it perfectly clear what I'm claiming my code does:
func testMyClassInit() {
let subject = MyClass(fetcher: fetcher)
wait(subject.result == "howdy")
XCTAssertEqual(subject.result, "howdy") // redundant but nice
}
Tnx to matt this is the correct way. No need for async in the test function and just using a predicate did the job.
func testFetching() {
let items = [Item(), Item()]
let fakeFetching = FakeFetching(returnValue: items)
let expectation = XCTestExpectation()
let vm = ViewModel(fetching: FakeFetching())
let pred = NSPredicate { _, _ in
vm.items == items
}
let expectation = XCTNSPredicateExpectation(predicate: pred, object: vm)
wait(for: [expectation], timeout: 1)
}
Slight variation on Matt's excellent answer. In my case, I've broken out his extension method into even more granular extensions for additional convenience.
Helper Framework
public typealias Predicate = () -> Bool
public extension NSPredicate {
convenience init(predicate: #escaping #autoclosure Predicate) {
self.init{ _, _ in predicate() }
}
}
public extension XCTNSPredicateExpectation {
convenience init(predicate: #escaping #autoclosure Predicate, object: Any) {
self.init(predicate: NSPredicate(predicate: predicate()), object: object)
}
convenience init(predicate: #escaping #autoclosure Predicate) {
self.init(predicate: NSPredicate(predicate: predicate()))
}
convenience init(predicate: NSPredicate) {
self.init(predicate: predicate, object: nil)
}
}
public extension XCTestCase {
func XCTWait(for condition: #escaping #autoclosure Predicate, timeout: TimeInterval = 10) {
let expectation = XCTNSPredicateExpectation(predicate: condition())
wait(for: [expectation], timeout: timeout)
}
}
With the above in place, the OP's code can be reduced to this...
Unit Test
func testFetching() {
let items = [Item(), Item()]
let fakeFetching = FakeFetching(returnValue: items)
let vm = ViewModel(fetching: FakeFetching())
XCTWait(for: vm.items == items, timeout: 1)
}
Notes on Naming
Above, I'm using a somewhat controversial name in calling my function XCTWait. This is because the XCT prefix should be considered reserved for Apple's XCTest framework. However, the decision to name it this way stems from the desire to improve its discoverability. By naming it as such, when a developer types XCT In their code editor, XCTWait is now presented as one of the offered auto-complete entries** making finding and using much more likely.
However, some purists may frown on this approach, citing if Apple ever added something named similar, this code may suddenly break/stop working (although unlikely unless the signatures also matched.)
As such, use such namings at your own discretion. Alternately, simply rename it to something you prefer/that meets your own naming standards.
(** Provided it is in the same project or in a library/package they've imported somewhere above)

How to define global final variable that not changes for all tests under class?

I write unit test in Swift.
I call my app method and through delegate get back JSON object that represents request.
Now I want to validate all fields of JSON. Each validation should be in separate test.
This is what I wrote:
class LaunchTests: XCTestCase, TestServerHandlerDelegate {
var theExpectation:XCTestExpectation?
var launchRequest:String? = nil
public func onSend(_ data: Data!) {
launchRequest = NSString(data: data, encoding: String.Encoding.utf8.rawValue)
theExpectation?.fulfill()
}
override func setUp() {
super.setUp()
// we wait in setUp till get 'launchRequest'
if launchRequest == nil {
theExpectation = expectation(description: "initialized")
MyApp.shared().setDelegate(self)
MyApp.shared().launch()
// Loop until the expectation is fulfilled in onDone method
waitForExpectations(timeout: 500, handler: { error in XCTAssertNil(error, "Oh, we got timeout")})
}
}
override func tearDown() {
super.tearDown()
}
func test___01_platform(){
if let _ = fetchJsonValue(key: "somekey", value: launchRequest){
//...
}
}
func test___02_platform(){
if let _ = fetchJsonValue(key: "platform", value: launchRequest){
//...
}
}
The problem is: for each run launchRequest is nil. I know that its right behavior but I want to call MyApp.shared().launch() once only and run multiple tests for launchRequest data.
How can I achieve it?
(I know its not good practice for unit testing but anyways)
Thanks,
Well, if you really want that, you can just put launchRequest outside of your LaunchTests class:
var launchRequest:String? = nil
class LaunchTests: XCTestCase, TestServerHandlerDelegate {
...
}
The solution was to define launchRequest as static (as Maxim Shoustin mentioned):
static var launchRequest:String? = nil
In this case everything works as expected