Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

xcode

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?

like image 390
Kwnstantinos Nikoloutsos Avatar asked Sep 06 '25 03:09

Kwnstantinos Nikoloutsos


2 Answers

Late to the party but this worked for me

    let task = Task {
        sut.submitButtonPressed()
    }
    await task.value
    XCTAssertTrue(mockInteractor.fetchSectionsWasCalled)
like image 135
Andre White Avatar answered Sep 07 '25 23:09

Andre White


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()
        
        mockedInteractor.$fetchSectionsIsCalled
            .sink { isCalled in
                if isCalled {
                    methodCallExpectation.fulfill()
                }
            }
            .store(in: &cancellable)
        
        //acte
        myClass.submitButtonPressed()
        wait(for: [methodCallExpectation], timeout: 1)
        
        //assert
        XCTAssertTrue(mockedInteractor.fetchSectionsIsCalled)
    }
like image 31
BEN MESSAOUD Mahmoud Avatar answered Sep 07 '25 23:09

BEN MESSAOUD Mahmoud