如何在 swift 中测试包含 Task Async/await 的方法

Kwn*_*sos 22 xcode swift

给定以下包含Task 的方法。

  • self.interactor被嘲笑。
func submitButtonPressed() {
    Task {
        await self.interactor?.fetchSections()
    }
}
Run Code Online (Sandbox Code Playgroud)

我如何编写一个测试来验证 fetchSections ()是从该方法调用的?!

我的第一个想法是使用期望并等待它被实现(在模拟的代码中)。

但是对于新的 async/await 有没有更好的方法呢?

小智 12

聚会迟到了,但这对我有用

    let task = Task {
        sut.submitButtonPressed()
    }
    await task.value
    XCTAssertTrue(mockInteractor.fetchSectionsWasCalled)
Run Code Online (Sandbox Code Playgroud)


BEN*_*oud 7

我不知道您是否已经找到问题的解决方案,但这是我对面临同样问题的其他开发人员的贡献。

我的情况和你一样,我通过使用Combine通知被测试的类该方法被调用来解决了这个问题。

假设我们有这个方法来测试:

func submitButtonPressed() {
    Task {
        await self.interactor?.fetchSections()
    }
}
Run Code Online (Sandbox Code Playgroud)

我们应该从模拟交互开始:

import Combine

final class MockedInteractor: ObservableObject, SomeInteractorProtocol {
    @Published private(set) var fetchSectionsIsCalled = false

    func fetchSection async {
        fetchSectionsIsCalled = true
        // Do some other mocking if needed
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们有了模拟的交互器,我们可以开始编写单元测试:

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)
    }
Run Code Online (Sandbox Code Playgroud)


bau*_*sic 6

这里建议了一种解决方案(@andy),涉及注入Task. 有一种方法可以通过func执行返回Task并允许测试 的await任务来做到这一点value
(我并不热衷于更改可测试的类以适应测试(返回Task),但它允许在async没有NSPredicate或设置一些任意期望时间(这只是气味)的情况下进行测试)。

@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)
}
Run Code Online (Sandbox Code Playgroud)


mat*_*att 0

理想情况下,正如您所暗示的,您interactor将使用协议进行声明,以便您可以替换模拟以用于测试目的。然后,您查阅模拟对象以确认所需的方法已被调用。通过这种方式,您可以正确限制被测系统的范围,以仅回答“这个方法被调用了吗?”的问题。

至于测试方法本身的结构,是的,这仍然是异步代码,因此需要异步测试。所以使用期望并等待它是正确的。您的应用程序使用 async/await 来表达异步性这一事实并没有神奇地改变这一点!(您可以通过编写一个创建 BOOL 谓词期望并等待它的实用方法来减少其冗长性。)

  • 当函数没有完成块时,您将如何使用期望?我想你可以等待任意时间,但这似乎很脆弱。 (4认同)