I’ve written a book to teach you how to write unit tests effectively. If you want to make your tests fast so you’ll actually run them and run them often to receive immediate feedback, check out Effective Unit Testing. You’ll develop the confidence that your change will not break existing features, and never have to worry about introducing regression. Write non-fragile unit tests that are assets, not liabilities. Use TDD to write testable code that drives feature development.
Now that you’ve tested the interactor and presenter in the Clean Swift VIP cycle. It’s time to finish up with the star of the show.
Drum roll please… View Controller
You’re going to finally take down the massive view controller. Massive leads to a massive post. So I’ll break this post into two parts:
In part 1, you’ll see the familiar setup and spy. You’ll then write tests for the expiration date picker to make sure a date is formatted as desired. We’ll diverge to look at the new invisible UI component and how it comes into play when testing view controllers. Finally, you’ll finish up by writing tests for the shipping method picker.
In part 2, you’ll write tests to make sure the keyboard behave as expected in all edge cases in which the text fields become first responder. You’ll also learn how to make running tests the same as running the app. Finally, you’ll test to make sure the pickers are configured properly.
Let’s get started.
Create the XCTestCase for the view controller
In Xcode, choose File -> New -> File, then select Test Case Class. Enter CreateOrderViewControllerTests
for class. Make sure the CleanStoreTests target is checked. Click Create. The CreateOrderViewControllerTests.swift
file is created.
The CreateOrderViewController
’s input and output protocols are defined as follows:
|
protocol CreateOrderViewControllerInput { func displayExpirationDate(viewModel: CreateOrder_FormatExpirationDate_ViewModel) } protocol CreateOrderViewControllerOutput { var shippingMethods: [String] { get } func formatExpirationDate(request: CreateOrder_FormatExpirationDate_Request) } |
Setup and Spy
With the interactor and presenter, setting up the subject under test is as simple as instantiating them, like CreateOrderInteractor()
and CreateOrderPresenter().
In fact, the Clean Swift configurator creates them in the same way before hooking them up to the VIP cycle for real use. They are just POSO – Plain Old Swift Object – with no parent class.
However, CreateOrderViewController
inherits, either directly or indirectly, from UIViewController
. It is loaded from the storyboard instead of created programmatically. This loading does a lot of things behind the scenes. Building the view hierarchy. Setting up the responder chain. Displaying the view on the screen. Among others.
Simply instantiating a view controller with CreateOrderViewController()
doesn’t really fully set it up, ready to be used or tested.
But if you were to do all these things manually, you would need to dig deep into Apple’s documentation on view and view controller life cycles.
Fortunately, you don’t have to do that. You can just load it from the storyboard when you are running tests as if you are running the app.
|
func setupCreateOrderViewController() { let bundle = NSBundle(forClass: self.dynamicType) let storyboard = UIStoryboard(name: "Main", bundle: bundle) createOrderViewController = storyboard.instantiateViewControllerWithIdentifier("CreateOrderViewController") as! CreateOrderViewController _ = createOrderViewController.view } |
You’ve seen similar code when you segue from one view controller to another. First, you get a bundle and a storyboard. And then call the instantiateViewControllerWithIdentifier()
method with an identifier which you give to your view controller you create in the storyboard. It is almost always a good idea to set the identifier to the view controller class name.
But there are two very, very important things happening on the last line:
Continue reading →