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
方面,有XCTestExpectation
class来测试异步函数。
但是您测试 Presenter 的方法存在问题。你应该为你的网络服务使用 mock 并用预期的参数存根它。调用实际服务没有意义。单元测试是最快的一种测试。私有方法是黑盒的一部分,你不应该关心它的内部结构。测试 public 接口但不测试私有方法。
如果您将尝试模拟和存根 APIService
,那么您会发现如果它是单例则不可能做到。您最终会将服务作为依赖项注入 Presenter 以获得更好的可测试性。
如果服务将被模拟并使用存根,则无需使用XCTestExpectation
,因为不会有任何异步代码。
你好,我正在尝试对位于 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
方面,有XCTestExpectation
class来测试异步函数。
但是您测试 Presenter 的方法存在问题。你应该为你的网络服务使用 mock 并用预期的参数存根它。调用实际服务没有意义。单元测试是最快的一种测试。私有方法是黑盒的一部分,你不应该关心它的内部结构。测试 public 接口但不测试私有方法。
如果您将尝试模拟和存根 APIService
,那么您会发现如果它是单例则不可能做到。您最终会将服务作为依赖项注入 Presenter 以获得更好的可测试性。
如果服务将被模拟并使用存根,则无需使用XCTestExpectation
,因为不会有任何异步代码。