【问题标题】:How do you test UIStoryBoard segue is triggered by didSelectRow(at:)你如何测试 UIStoryBoard segue 是由 didSelectRow(at:) 触发的
【发布时间】:2017-02-21 16:38:38
【问题描述】:

所以我有一个带有 UITableView 的故事板。有一个原型单元,其中一个 show segue 连接到另一个 UIViewController

示例

  • 小区标识符是“CellOne”
  • segues 没有标识符
  • 我的类是 tableView 的 dataSource 和 delegate。

类看起来像这样:

import UIKit

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    @IBOutlet weak var tableView: UITableView!

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 2
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        return tableView.dequeueReusableCell(withIdentifier: "CellOne", for: indexPath)
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("Selected the row")
    }
}

通常我会通过调配准备 segue 来捕获目标 ViewController 以及我需要的任何其他东西来测试它,但以编程方式调用 tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 不会触发准备 segue 的代码。

是否可以在不添加 segue 标识符并从 prepareForSegue 显式调用它的情况下测试选择 Cell One 会触发故事板 segue?

【问题讨论】:

  • 嘿,你的问题不清楚!为什么要测试 segue 是否由 didSelectRow 触发?这很明显,因为你在那个 VC 中只有一个 segue。那么,您的问题是检测哪些 tableviewcell 触发器 prepareforSegue ?
  • @JenJose 这是一个人为的例子,真正的项目有更多的单元格,自定义单元格等......
  • 我也遇到了同样的问题,你找到解决办法了吗?
  • @JordiPuigdellívol,更新了我的答案,包括我用来解决这个问题的一些方法。
  • 感谢您的回答,我一直在深入研究它,正如您所说,没有“好的”方法来测试它,所以我选择了单元格引用并在测试自己,问题是你无法测试单元格是否有连接的动作,但至少是干净的

标签: ios swift unit-testing


【解决方案1】:

如果您的 tableViewCell 是唯一触发到目的地的 segue 的东西,您可以使用 isas

if let destination = segue.destination as? MyViewController,
   let indexPath = tableView.indexPathForSelectedCell {
     destination.detail = model[indexPath.row]
}

否则,如果您需要消除歧义,可以使用 isas

检查发件人的类别

【讨论】:

    【解决方案2】:

    更新: 长话短说,没有一个很好的方法可以做到这一点。让这变得困难的是,虽然我们习惯于测试的大多数控件都有一个动作和一个发送者,但对 UITableViewCell 的触摸是一种不同的范例。

    也就是说,拥有 segue 标识符基本上是任何策略的先决条件。

    一种方法是获取对单元格的引用并调用performSegue(withIdentifier:,sender:)

    class ViewControllerTests: XCTestCase {
    
        func testClickingACell() {
            let controller = UIStoryboard(name: "ViewController", bundle: nil).instantiateInitialViewController() as! ViewController
            let cell = controller.tableView.dataSource?.tableView(controller.tableView, cellForRowAt: IndexPath(row: 0, section: 0))
    
            controller.performSegue(withIdentifier: "MySegue", sender: cell)
    
            XCTAssertNotNil(controller.presentedViewController as? TheDestinationViewController)
        }
    }
    

    另一种(完全矫枉过正)的方法是拥有一个自定义单元格,您可以在其中处理所有自己的触摸逻辑。这将是疯狂的,但这是一种可能性,它将为测试开辟更多选择。我不会以这种方式展示,因为这样做会很疯狂。

    另一种方法是使用不同的架构,摆脱UIKit 并允许仅测试performSegue 逻辑。示例:

    class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    
        @IBOutlet var myTableView: UITableView!
        var navigator: UIViewController?
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            navigator = self
        }
    
        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
            navigator?.performSegue(withIdentifier: "MySegue", sender: nil)
        }
    
    }
    

    这允许您在测试中执行类似的操作:

    class MockNavigator: ViewController {
        var performSegueCalled = false
        var performSegueIdentifier: String?
    
        override func performSegue(withIdentifier identifier: String, sender: Any?) {
            performSegueCalled = true
            performSegueIdentifier = identifier
        }
    }
    
    func testExample() {
        let controller = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController() as! ViewController
        controller.loadViewIfNeeded()
    
        // Need to keep a reference to be able to assert against it
        let mockNavigator = MockNavigator()
    
        controller.navigator = mockNavigator
    
        controller.tableView(controller.myTableView, didSelectRowAt: IndexPath(row: 0, section: 0))
    
        XCTAssertTrue(mockNavigator.performSegueCalled)
        XCTAssertEqual(mockNavigator.performSegueIdentifier, "MySegue")
    }
    

    另一种构建代码以避免 UIKit 的方法是使用类似于视图模型协调器模式的东西来创建和测试视图模型。基本上你会告诉你的协调器一个单元格被选中,并且协调器将使用所需的 segue 标识符更新视图模型。通过这种方式,您可以测试您的协调器对象,并在很大程度上确保如果协调器已连接,您将触发正确的 segue。一个简单的手动测试会告诉你。

    在伪代码中:

    struct ViewModel {
        let labelText: String
        let segueIdentifier: String
    }
    
    class Coordinator {
        var data = [YourObject]()
        var viewModel = ViewModel(labelText: "", segueIdentifier: "")
    
        func selectedItem(at row: Int) {
            let item = data[row]
    
            // Do some logic to figure out which identifier you want
            var segueIdentifer: String
            if item == whatever {
                segueIdentifier = "something"
            }
            viewModel = ViewModel(labelText: item.text, segueIdentifier: segueIdentifier)
        }
    }
    

    可能最好的方法是多种方法的组合。使用带有自行测试的视图模型的协调器。然后进行测试,您使用 UIKit 选择一个单元格,并确保按预期使用该协调器的模拟实现。您一次测试的单元越小就越容易。

    【讨论】:

      【解决方案3】:

      如果只是测试一个 segue,你可以这样做:

      在你的 VC 中:

      open let segueToSomewhere = "segueToSomewhere"
      open var calledSegue: UIStoryboardSegue!
      
      override open func prepare(for segue: UIStoryboardSegue, sender: Any?) {
          super.prepare(for: segue, sender: sender)
          calledSegue = segue
      }
      

      在您的测试中:

      func testYourSegue() {
          //Given
          let storyboard = UIStoryboard(name: "Your_storyboard", bundle: nil)
          let yourVC = storyboard.instantiateViewController(withIdentifier: "YourVC") as? YourVC
      
          //When
          yourVC.performSegue(withIdentifier: previousVC.segueToSomewhere, sender: nil)
      
          //Then
          XCTAssertEqual(previousVC.calledSegue.identifier, yourVC.segueToSomewhere, "The selected segue should be \(previousVC.segueToSomewhere)")
      }
      

      【讨论】:

        猜你喜欢
        • 2019-10-07
        • 2012-06-17
        • 2013-11-11
        • 1970-01-01
        • 2017-02-12
        • 2014-11-29
        • 1970-01-01
        • 2012-11-08
        • 1970-01-01
        相关资源
        最近更新 更多