Test-Driven iOS: Using Segues

Reading Time: 5 minutes

When we build storyboards in iOS to wireframe and illustrate the flow of our apps, we can use segues to create transitions between screens and their elements. But if we want to unit test those transitions, we have to do it differently than we would if we were manhandling the transition programmatically with presentViewController().

So how do we do it? With three steps.

Step 1: Write a Test For Your Screen Transition

First, we’ll write a test to make sure that our transition does what we want it to do. I’ll show you the test, and then we’ll walk through what is happening in the test.

For this test I am using XCode’s default test runner, XCTest, and the Hamcrest matcher library (which provides me with the assertThat() method that you see below).


import XCTest
import Hamcrest
@testable import MyExampleApp
class ExampleViewControllerTest: XCTestCase {
func testNavigatesToOtherScreen() {
let controller = ExampleViewController.loadFromStoryboard()
assertThat(controller.view, present())
UIWindow.present(viewController: controller) { () in
controller.didTapButtonToGoToOtherViewController(NSObject())
assertThat(controller.presentedViewController, presentAnd(instanceOf(OtherViewController)))
}
}
}

What’s happening here?

let controller = ExampleViewController.loadFromStoryboard()

Here we are loading up our controller with a class function we made called loadFromStoryboard(). This method obtains your controller based on its view in the storyboard, so that your segues and elements match up. The method comes in handy for controlling the life cycle of our View Controller inside a test. It’s also useful for dependency injection. More on that in later posts.

assertThat(controller.view, present()) 

This is a safety check that I put in so that, if my test fails, I know whether it’s because I failed to load and present my view properly, or because I did not yet implement the behavior defined in my test. I don’t like to guess why my test failed.

UIWindow.present(viewController: controller) { () in 

This block presents the view controller. The way XCTest works is that it fires up your app, but it doesn’t do any navigation of its own accord. So if the screen you are testing is not the launch screen, it’s not going to show up automatically. This call forces the window to present the controller that you instantiated with loadFromStoryBoard(), so you can test just that view without navigating through the app. Everything inside this block gets executed on the presented view controller.

controller.didTapButtonToGoToOtherViewController(NSObject()) 

Here we are calling the method that will trigger our segue. It’s an @IBAction method associated with pressing a button on the view. This is wired up from the storyboard to the code in the normal fashion…by control-clicking the button and dragging over to the ViewController, then selecting IBAction and naming the method  didTapButtonToGoToOtherViewController(). You can implement segues with any element though—not just buttons. In the test, call whatever gesture, tap, or other event you want to associate with your segue.

To get the test to compile, you’ll have to add this method to your ViewController…even if it’s not implemented or even linked to the storyboard. Don’t worry…as long as it’s not implemented and linked, your test will fail on the next line.

assertThat(controller.presentedViewController, presentAnd(instanceOf(OtherViewController))) 

Here’s the special sauce—we assert that the last presented view controller is our destination view controller.  If we run the test right now, before implementation, it should fail on this line.


Step 2: Set Up Your Segue in Storyboard

Now comes the fun part: we make our segue with interface builder. When we’re done, our transition will be represented with a nice arrow:

screen-shot-2017-01-24-at-8-14-12-pm

Chelsea, I know how to do this already; I just control-drag from my element to the screen where I want to go.”

STAHHHHP.

You want to manually trigger the segue inside the code of your ViewController. So your segue is going to connect from the whole origin view controller— and not a specific element on its view—to the destination view controller.

Control-click on this button in the origin view controller:

screen-shot-2017-01-24-at-8-14-38-pm

…and drag to your destination view controller.

Then, in the right panel, give your shiny new segue a name:

screen-shot-2017-01-24-at-8-12-38-pm

Step 3: Trigger Your Segue

Now we connect our segue to our element…but we do it in code, instead of in the storyboard. Here I have done it with an IBAction on a button on my view.

If you want to connect it to something that’s not a button, you can also call performSegueWithIdentifier() inside the body of any gesture recognizer.

WARNING: the first argument of performSegueWithIdentifier() must exactly match the name you gave your segue. This can be a little difficult to troubleshoot, so if you have lower case L’s, upper case i’s (or any kind of i’s really), m’s or n’s in your name, check this very closely.


class ExampleViewController: UIViewController {
@IBAction func didTapButtonToGoToOtherViewController(sender: AnyObject) {
self.performSegueWithIdentifier("otherViewControllerSegue", sender: nil)
}
}

OK, once you have this code in, run your test. It should pass! Now you have all the benefits of a segue screen transition—including storyboard representation, customized animation, transition error handling, and the rest—in a format that you can check with a unit test.

 

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.