iosswiftunit-testingmvppresenter

iOS - Unit Testing asynchronous private function in Presenter of MVP


Hello I'm trying to unit testing a private function which located in Presenter

This is my Presenter Codes and I'm using Networking Singleton Object 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)
      }
    }
  }
}

and this is my ViewController codes

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)
  }
}

These are my Presenter and View. And I wrote Test Code like this

First, I made Mock PassiveView Like this

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
  }

}

So I expected that if I could test Presenter's request(_:) methods with valid id and invalid

but because request(_:) didn't get handler parameter and APIService.shared.fetch is asynchronous, I couldn't get correct result by calling request(_:). (Always false)

How can I test this kind of Presenter?


Solution

  • In terms ofXCTests, there is the XCTestExpectation class to test asynchronous functions. But there is an issue in your approach of testing the Presenter. You should use mock for your network service and stub it with expected arguments. It doesn't make sense to call the actual service. Unit tests are the fastest kind of tests. Private methods are a part of black-box which you should not care about its internal structure. Test public interfaces but don't test private methods. If you will try to mock and stub APIService, then you notice it's impossible to do if it's a singleton. You'll end up with injecting service as a dependency into Presenter for the better testability.

    If the service will be mocked and the stub will be used, then there is no need in using XCTestExpectation, because there won't be any asynchronous code.