简体   繁体   中英

How to test a function that gets into the main thread in Swift with RxSwift and XCTest?

I came across this problem when testing my View:

In my ViewModel I call to an asynchronous operation and when the response arrives, I use a PublishSubject to produce a change in my View. In my View, I call DispatchQueue.main.async in order to hide or show a button.

ViewModel

let refreshButtons = PublishSubject<Bool>(true)
refreshButtons.onNext(true)

View

model.refreshButtons.asObservable()
                .subscribe(onNext: {
                    [unowned self] success in
                    self.updateButtons(success)
                })
                .addDisposableTo(disposable)

private func updateButtons(_ show:Bool) {
   DispatchQueue.main.async{
      button.isHidden = !show
   }
}

Now I don't know how to unit test that refreshButtons.onNext(true) will hide or show my button.

The solutions I can think of are:

  • Overriding the method and having an async expectation, but for that I need to make the method public, what I don't want, or
  • Dispatching the main queue in my ViewModel and not in the view, what it sounds odd to me, but might me ok.

How can I solve this? Thank you in advance.

You could use an async expectation based on a predicate in your unit test to wait an see if the button is not hidden anymore.

func testButtonIsHidden() {
  // Setup your objects
  let view = ...
  let viewModel = ...

  // Define an NSPredicate to test your expectation
  let predicate = NSPredicate(block: { input, _ in
    guard let _view = input as? MyView else { return false }
    return _view.button.isHidden == true
  })


  // Create an expectation that will periodically evaluate the predicate 
  // to decided whether it's fulfilled or not
  _ = expectation(for: predicate, evaluatedWith: view, handler: .none)

  // Call the method that should generate the behaviour you are expecting.
  viewModel.methodThatShouldResultInButtonBeingHidden()

  // Wait for the 
  waitForExpectationsWithTimeout(1) { error in
    if let error = error {
      XCTFail("waitForExpectationsWithTimeout errored: \(error)")
    }
  }
}

Something worth noting is that the value you pass to the NSPredicate should be a class . That is because classes are passed by reference, so value inside the predicate block will be the same as the one touched by your view model. If you were to pass a struct or enum though, which are passed by copy, the predicate block would receive a copy of the value as it is at the time of running the setup code, and it will always fail.

If instead you prefer to use UI tests as suggested by @Randall Wang in his answer, then this post might be useful for you: " How to test UI changes in Xcode 7 ". Full disclosure, I wrote that post.

First of all, You don't need test private method

If you want to test if the button is hidden or not,try UI testing

here is the WWDC of UI testing.

https://developer.apple.com/videos/play/wwdc2015/406/

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM