Harry Blue
Harry Blue

Reputation: 4522

How can I simulate a tableView row selection using RxSwift

I have a UITableViewController setup as below.

View Controller

class FeedViewController: BaseTableViewController, ViewModelAttaching {

    var viewModel: Attachable<FeedViewModel>!
    var bindings: FeedViewModel.Bindings {
        let viewWillAppear = rx.sentMessage(#selector(UIViewController.viewWillAppear(_:)))
            .mapToVoid()
            .asDriverOnErrorJustComplete()

        let refresh = tableView.refreshControl!.rx
            .controlEvent(.valueChanged)
            .asDriver()

        return FeedViewModel.Bindings(
            fetchTrigger: Driver.merge(viewWillAppear, refresh),
            selection: tableView.rx.itemSelected.asDriver()
        )
    }

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    func bind(viewModel: FeedViewModel) -> FeedViewModel {
        viewModel.posts
            .drive(tableView.rx.items(cellIdentifier: FeedTableViewCell.reuseID, cellType: FeedTableViewCell.self)) { _, viewModel, cell in
                cell.bind(to: viewModel)
            }
            .disposed(by: disposeBag)

        viewModel.fetching
            .drive(tableView.refreshControl!.rx.isRefreshing)
            .disposed(by: disposeBag)

        viewModel.errors
            .delay(0.1)
            .map { $0.localizedDescription }
            .drive(errorAlert)
            .disposed(by: disposeBag)

        return viewModel
    }
}

View Model

class FeedViewModel: ViewModelType {

    let fetching: Driver<Bool>
    let posts: Driver<[FeedDetailViewModel]>
    let selectedPost: Driver<Post>
    let errors: Driver<Error>

    required init(dependency: Dependency, bindings: Bindings) {

        let activityIndicator = ActivityIndicator()
        let errorTracker = ErrorTracker()

        posts = bindings.fetchTrigger
            .flatMapLatest {
                return dependency.feedService.getFeed()
                    .trackActivity(activityIndicator)
                    .trackError(errorTracker)
                    .asDriverOnErrorJustComplete()
                    .map { $0.map(FeedDetailViewModel.init) }
        }

        fetching = activityIndicator.asDriver()
        errors = errorTracker.asDriver()
        selectedPost = bindings.selection
            .withLatestFrom(self.posts) { (indexPath, posts: [FeedDetailViewModel]) -> Post in
                return posts[indexPath.row].post
        }
    }

    typealias Dependency = HasFeedService

    struct Bindings {
        let fetchTrigger: Driver<Void>
        let selection: Driver<IndexPath>
    }
}

I have binding that detects when a row is selected, a method elsewhere is triggered by that binding and presents a view controller.

I am trying to assert that when a row is selected, a view is presented, however I cannot successfully simulate a row being selected.

My attempted test is something like

   func test_ViewController_PresentsDetailView_On_Selection() {

        let indexPath = IndexPath(row: 0, section: 0)
        sut.start().subscribe().disposed(by: rx_disposeBag)
        let viewControllers = sut.navigationController.viewControllers

        let vc = viewControllers.first as! FeedViewController
        vc.tableView(tableView, didSelectRowAt: indexPath)

        XCTAssertNotNil(sut.navigationController.presentedViewController)
    }

But this fails with the exception

unrecognized selector sent to instance

How can I simulate the row is selected in my unit test?

Upvotes: 1

Views: 974

Answers (1)

Daniel T.
Daniel T.

Reputation: 33979

The short answer is you don't. Unit tests are not a place for UIView objects and UI object manipulation. You want to add a UI Testing target for that sort of test.

Upvotes: 3

Related Questions