How to slim down your viewDidLoad() method

You’ve probably seen a bloated viewDidLoad() method that’s doing eight different things such as:

  • Fetch data over the network
  • Display the data in the table view
  • Update the contents in Core Data
  • Upload photos from camera or albums
  • Style table view cells with complex subviews
  • Execute different branches depending on whether the user is logged in or not
  • Check IAP to see which products should be made available
  • Fire off some background tasks

It’s impossible to write unit tests for a bloated viewDidLoad() method like this. Even if you manage to do so by plowing through the frustrations, the resulting tests won’t be of much value. The reason is simple. The behaviors are complex and inconsistent. There are too many use cases lumped into one single method. You won’t trust your tests to give you the confidence you need to ensure you aren’t breaking anything when making changes.

If you ever wonder about these two questions:

  • How to refactor the viewDidLoad() method to slim it down?
  • How do you write unit tests for it?

Then you should read on to learn the 1-2-3 step process to improve your code.

Continue reading →

Advanced Dependency Injection

In my last post, I showed you how to use setter dependency injection to mock an Apple built-in class by:

  • Creating a super simple test double by hand
  • Extracting the dependency as an instance variable
  • Inject the test double in the Given phase

Two astute readers wrote in about a problem in this example. I’ll describe the problem, a workaround, and two solutions in this post. I’ve also created a sample project called WhereIsMyApple on GitHub. This app has one scene named List that shows some Apple retail stores in a table view. When the user taps on a row, the app displays the selected retail store in Apple Map.

Quick Recap

Here is the MapViewController in my last post again. I’m showing only the relevant code here.

The displayLocateStore(viewModel:) method extracts the coordinate and address data from the viewModel argument. It then creates a MKPlacemark object that is used to instantiate a new MKMapItem instance. Next, it sets the name property, and invokes the openInMaps(launchOptions:) method. The effect is to display the store in the Apple map.

The simplest test double that we can create is as follows:

The MapItemSpy class inherits from MKMapItem, and overrides the openInMaps(launchOptions:) method. It sets openInMapsCalled to true when the method is called.

Continue reading →

Book Bundle Pricing

The pricing for my books individually, and as a bundle, have changed. So the following post doesn’t apply anymore. Please refer to The Clean Swift Handbook and Effective Unit Testing for details.

First thing first. I recently released my new book Effective Unit Testing in December. If you already bought it, you’ll get a huge update (almost double the number of pages) this week on the following topics:

  • Updated the chapter on generating test files
  • Added a new chapter on building and running your unit tests
  • Described how to edit the scheme to make your unit tests run faster
  • Added a login form example to describe the relationship IBActions and IBOutlets have to the view controller
  • Added a new chapter on test driven development
  • Added a new chapter on TDD-ing the ShowOrder Scene

Naturally, some of you have asked if there is a bundle pricing available if you want to get Effective Unit Testing and the Clean Swift Handbook together. I’ve certainly thought about offering a special deal to customers who want to buy more stuff from me. (Read on for a special bundle price.) At the same time, I always stick to my principle of giving the best deal to my early and existing customers. And I’m not going to violate that principle. As you can imagine, that creates a sticky situation.

I can segment my customers into the following six groups:

  1. People who bought both books during the one-week early bird launch period at 50% discount. $39 + $39 = $78
  2. People who bought one book during early bird at 50% and the other book at regular price. $39 + $79 = $118
  3. People who bought just one book during early bird. $39
  4. People who bought just one book at regular price. $79
  5. People who bought both books at regular price. $79 + $79 = $158
  6. People who haven’t bought but are interested in getting both books at bundle price

If I offer bundle pricing for buying both books, it obviously has to be lower than $158 for it to make sense. But it also can’t be lower than $118 because that would violate my principle. People may have missed the early bird launch for one of the books but still end up buying both books, regardless of pricing. These customers who already bought both books are loyal customers, and I need to treat them well.

So I decide the bundle price is going to be somewhere between $118 and $158.

Continue reading →

How to mock an Apple built-in class

The MKMapItem class is an Apple built-in class. How do I make sure the openInMaps(launchOptions:) method is being invoked on MKMapItem when I test the displayRoute(viewModel:) method?

You’re passing in lat and lng in the viewModel, and then use them to create a Placemark then MKMapItem. This means all inputs are under your control. The output is the fact that the openInMaps(launchingOptions:) method is invoked on a MKMapItem object. When you test the displayRoute(viewModel:) method, you don’t care what the openInMaps(launchingOptions:) method does. You only care that it’s being invoked.

Continue reading →

MVC doesn’t lend itself well to unit testing

Every beginning iOS developer learns to build their first app using MVC because that’s the way it is taught in guides and tutorials. But as the app evolves out of a prototype, there are more and more new features added and bugs discovered. Over time, all code is just added to the view controller because it’s convenient. You don’t need to create a new file, choose which folder/group to put the file in, design new classes and models, think about how they interact. Putting all code in the view controller becomes a bad habit. And we all know how difficult it is to change habits. As a result, massive view controllers are born.

A typical massive view controller has the following things:

  • View lifecycle events such as viewDidLoad(), viewDidAppear(_:), …etc.
  • Table view data source and delegate methods
  • IBOutlet variables to change UIView subclasses attributes
  • IBAction methods to handle user driven touch events
  • More if your view controller also deals with maps, notifications, …etc.
  • And any business logic you leave in the vie controller such as Core Data and networking

In the end, a massive view controller has everything in it. When the view controller gets to this kind of complexity, the beginning iOS developers may start looking into writing unit tests, or doing TDD for the app. That’s where the problem is. Their view controllers are already hooked up to a breathing tube. They are trying to write tests for untestable code. It ain’t gonna work. Tests are hard to write and they are fragile. So they finally give up! Going back to adding even more to the view controller.

So, what can you do about it?

Continue reading →