所以我有一个带UITableView的故事板。有一个原型单元格,show segue连接到另一个UIViewController
class看起来像这样:
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")
}
}
通常情况下,我会通过swizzling准备segue来捕获目标ViewController以及我需要的任何其他东西但是以编程方式调用tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
并不会触发准备segue的代码。
是否有可能测试选择Cell One触发故事板segue而不添加segue标识符并从prepareForSegue中明确调用它?
如果您的uitableViewCell是唯一触发到目的地的segue的东西,您可以使用或者:
if let destination = segue.destination as? MyViewController,
let indexPath = tableView.indexPathForSelectedCell {
destination.detail = model[indexPath.row]
}
否则,如果您需要消除歧义,可以使用is或as检查发件人的类别
更新:长话短说没有很好的方法来做到这一点。让这很困难的是,虽然我们习惯于测试的大多数控件都有动作和发送者,但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选择一个单元格,并确保按预期使用该协调器的模拟实现。您一次测试的较小单元越容易。