向下转换/子类UIViewController,用于单元测试中的模拟

时间:2019-04-08 21:32:05

标签: ios swift unit-testing xctest

我有一个单元测试,我想创建UIViewController的子类版本,例如Test1ViewController。具体来说,我想重写此类的present方法。

我有一个视图控制器扩展,它根据其类名实例化一个视图控制器。

public class func instanceFromStoryboard<T>(storyboard: Storyboard) -> T {
    return UIStoryboard(name: storyboard.rawValue, bundle: nil).instantiateViewController(withIdentifier: String(describing: T.self)) as! T
}

还有一个Storyboard类。

public enum Storyboard: String {
    case main = "Main"
}

在单元测试中,我从Test1ViewController创建了一个子类。

class Test2ViewController: Test1ViewController {
    var presented: Bool = false
    override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
        presented = true
    }
}

如何使用扩展方法从情节提要中检索视图控制器,然后将其/子类降级到Test2ViewController

3 个答案:

答案 0 :(得分:1)

由于情节提要中的对象实际上是已编码的对象,因此无法将其解码并转换为其他任何对象。这是使用情节提要的缺点。放在情节提要中的就是您得到的内容。

如果可以,请使用基于XIB的视图控制器,而不要使用基于情节提要的视图控制器。使用XIB(以及编程视图控制器),测试可以实例化子类。

如果没有,那么您需要为视图控制器引入后门。这将是不幸的,因为这意味着将测试代码混入您的生产代码中。

答案 1 :(得分:1)

@Jon Reidanswer很好地总结了使用情节提要的局限性。

如果您的最终目标是验证受测的UIViewController是否存在某些东西,您是否考虑过检查presentedViewController属性?

// Create an asynchronous expectation to verify the view controller has presented
// something.
_ = expectation(
  for: NSPredicate(
    block: { input, _ -> Bool in
      guard let viewController = input as? UIViewController else { return false }
        // If you care about the type of the presented view controller you could
        // use `is` here to verify it
        return viewController.presentedViewController != nil
      }
    ),
    evaluatedWith: viewControllerUnderTest,
    handler: .none
)

viewControllerUnderTest.doSomething()

waitForExpectations(timeout: 1, handler: nil)

另一种方法是使用代表进行所有演示,该方法需要花费更多的工作,而且还可以使视图控制器在导航流程中更轻松地移动,从而随着时间的流逝而获得回报。

在测试中,您将不会检查是否已呈现任何内容,仅当使用Spy test double调用了呈现某些内容的导航委托方法时才可以。 如果您对此方法感到好奇,很乐意提供更多详细信息。

答案 2 :(得分:0)

@mokagioanswer应该可以工作。我更喜欢另一种方法。

func test_presentationOfViewController() {
  // Arrange
  let window = UIWindow(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
  window.rootViewController = sut
  window.makeKeyAndVisible()

  // Act
  sut.presentNext()

  // Assert
  XCTAssertTrue(sut.presentedViewController is DetailViewController)
}