你如何测试UIStoryBoard segue是由didSelectRow(at:)

时间:2017-02-21 16:38:38

标签: ios swift unit-testing

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

示例 enter image description here

  • 单元格标识符为“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的代码。

是否可以测试选择Cell One是否触发故事板segue而不添加segue标识符并从prepareForSegue中明确调用它?

3 个答案:

答案 0 :(得分:1)

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

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

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

检查发件人的类别

答案 1 :(得分:0)

<强>更新 长话短说,这不是一个很好的方法。让这很困难的是,尽管我们用于测试的大多数控件都有动作和发送者,但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的另一种方法是使用类似视图模型协调器模式的东西来创建和测试viewModel。基本上,您告诉您的协调员已选择一个单元格,协调员将使用所需的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选择一个单元格,并确保按预期使用该协调器的模拟实现。您一次测试的较小单位就越容易。

答案 2 :(得分:0)

仅用于测试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)")
}