iOS - 在MVP的Presenter中单元测试异步私有函数

iOS - Unit Testing asynchronous private function in Presenter of MVP

你好,我正在尝试对位于 Presenter

中的私有函数进行单元测试

这是我的演示者代码,我正在使用网络单例对象 APIService

class MyPresenter {
  weak var vc: MyProtocol?

  func attachView(vc: MyProtocol?) {
     self.vc = vc
  }

  func request(_ id: String) {
    if id.count == 0 {
      vc?.showIDEmptyAlert()
      return
    }
    fetch(id)
  }

  private func fetch(_ id:String) {
    DispatchQueue.global.async {
      APIService.shared.fetch(id) { (data, err) in 
        if let err = err {
          self.vc?.showErrorAlert()
          return
        }
        self.vc?.update(data)
      }
    }
  }
}

这是我的 ViewController 代码

class MyViewController: UIViewController, MyProtocol {
  private var presenter: MyPresenter = MyPresenter()

  override func viewDidLoad() {
    super.viewDidLoad()
    presenter.attachView(vc: self)
  }

  func showIDEmptyAlert() {
    self.present .. 
  }

  func showErrorAlert() {
    self.present .. 
  }

  func update(data: String) {
    self.label.text = data 
  }

  @IBAction func handleRegisterButton(_ sender: UIButton) {
    guard let id = idTextField.text else { return }
    presenter.request(id)
  }
}

这些是我的 Presenter 和 View。我写了这样的测试代码

首先,我像这样制作了 Mock PassiveView

class MyViewMock: MyProtocol {
  private (set) var showEmptyIdAlertHasBeenCalled = false
  private (set) var showErrorAlertHasBeenCalled = false
  private (set) var updateHasBeenCalled = false

  func showEmptyIdAlert() {
    showEmptyIdAlertHasBeenCalled = true
  }
  func showErrorAlert() {
    showErrorAlertHasBeenCalled = true
  }
  func update(data: String) {
    updateHasBeenCalled = true
  }

}

所以我希望如果我可以用有效的 id 和无效的

测试 Presenter 的 request(_:) 方法

但是因为request(_:)没有获取handler参数,而且APIService.shared.fetch是异步的,所以无法获取 通过调用 request(_:) 更正结果。 (始终为假)

如何测试这种Presenter?

要测试异步方法,您必须使用XCTestExpectation让您的测试等待异步操作完成。

这是一个测试异步方法的例子

// Asynchronous test: success fast, failure slow
func testValidCallToiTunesGetsHTTPStatusCode200() {
    // given
    let url = URL(string: "https://itunes.apple.com/search?media=music&entity=song&term=abba")
    // 1
    let promise = expectation(description: "Status code: 200")

    // when
    let dataTask = sessionUnderTest.dataTask(with: url!) { data, response, error in
        // then
        if let error = error {
            XCTFail("Error: \(error.localizedDescription)")
            return
        } else if let statusCode = (response as? HTTPURLResponse)?.statusCode {
            if statusCode == 200 {
                // 2
                promise.fulfill()
            } else {
                XCTFail("Status code: \(statusCode)")
            }
        }
    }
    dataTask.resume()
    // 3
    waitForExpectations(timeout: 5, handler: nil)
}

您可以从 here

阅读更多关于单元测试的内容

XCTests方面,有XCTestExpectationclass来测试异步函数。 但是您测试 Presenter 的方法存在问题。你应该为你的网络服务使用 mock 并用预期的参数存根它。调用实际服务没有意义。单元测试是最快的一种测试。私有方法是黑盒的一部分,你不应该关心它的内部结构。测试 public 接口但不测试私有方法。 如果您将尝试模拟和存根 APIService,那么您会发现如果它是单例则不可能做到。您最终会将服务作为依赖项注入 Presenter 以获得更好的可测试性。

如果服务将被模拟并使用存根,则无需使用XCTestExpectation,因为不会有任何异步代码。