Swift中的单元测试异步UI变更

uwopmtnx  于 2022-12-17  发布在  Swift
关注(0)|答案(1)|浏览(126)

我正在尝试对一个定制的UIView进行单元测试,它会异步地更改UI。

import UIKit

class DemoView: UIView {
    
    var label: UILabel!

    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setup()
    }
    
    func setup() {
        label = UILabel(frame: .zero)
        self.addSubview(label)
        label.translatesAutoresizingMaskIntoConstraints = false
        self.centerXAnchor.constraint(equalTo: label.centerXAnchor).isActive = true
        self.centerYAnchor.constraint(equalTo: label.centerYAnchor).isActive = true
    }
    
    @MainActor
    func setLabel(_ text: String) {
        Task {
            try await Task.sleep(for: .milliseconds(100))
            label.text = text
        }
    }
}

我想测试一下,在调用setLabel(_:)函数后,标签上的文本确实发生了变化,因此我编写了以下测试:

@MainActor
func testExample() async throws {
    let demoView = DemoView(frame: .zero)
    XCTAssertEqual(demoView.label.text, nil)
    
    demoView.setLabel("New Text")
    let expectLabelChange = expectation(for: NSPredicate(block: { _, _ in
        demoView.label.text != nil
    }), evaluatedWith: demoView.label)
    await waitForExpectations(timeout: 5)
    
    XCTAssertEqual(demoView.label.text, "New Text")
}

但是异常运行到超时,Assert失败。当我设置断点时,我可以看到setLabel(_:)中的Task被执行,但在休眠后从未重新进入,即使超时足够长。只有在waitForExpectations完成后,setLabel(_:)中的任务才继续,但这对Assert捕捉更改来说太晚了。
如何编写测试,以便setLabel(_:)中的Task继续?

**注意:**代码是为了演示这个问题而调整的。在真实的的应用程序中,我调用了一个API而不是休眠。

oymdgrw7

oymdgrw71#

你不需要async测试,也不应该使用它。setLabel不是async,你使用的是一个期望!这个测试会通过(我在这个过程中重写了一些小东西):

@MainActor func testExample() {
    let demoView = DemoView(frame: .zero)
    XCTAssertEqual(demoView.label.text, nil)

    demoView.setLabel("New Text")
    let predicate = NSPredicate { _, _ in
        demoView.label.text != nil
    }
    let expectLabelChange = expectation(for: predicate, evaluatedWith: nil)
    wait(for: [expectLabelChange], timeout: 5)

    XCTAssertEqual(demoView.label.text, "New Text")
}

更好的是,从setLabel调用中删除@MainActor;然后你也可以从测试函数中删除它。
在什么情况下async适合测试?如果setLabelasync!假设您将setLabel重写为:

func setLabel(_ text: String) async throws {
    try await Task.sleep(for: .milliseconds(100))
    label.text = text
}
  • 现在 * 你需要你的测试是async--现在你不需要期望了!看看一切变得多么简单:
@MainActor
func testExample() async throws {
    let demoView = DemoView(frame: .zero)
    XCTAssertEqual(demoView.label.text, nil)

    try await demoView.setLabel("New Text")
    XCTAssertEqual(demoView.label.text, "New Text")
}

相关问题