How to test your viewDidLoad() method

Even if you’ve been an iOS developer for one single day, you must’ve come across the situation where you need to do something when the viewDidLoad() method is called. Okay, easier enough. Just put some code in there.

But how do you test it? How can you ensure the viewDidLoad() method is called – your view is fully loaded – before your view controller tests execute?

This is very important. If viewDidLoad() is not called before your unit tests run, all tests fail. Nothing happens. You may also find that your tests sometimes pass, sometimes fail. And you have no idea why. This is likely a timing issue. The viewDidLoad() is called, but before some of your tests execute, and after some of your tests execute.

Read on to find out how to reliably test your view controller and its viewDidLoad() method.

Continue reading →

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

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 →