One of the most important things of unit testing is that it shouldn’t have side effects. If you don’t pay attention, you may have unexpected behaviours every time you run your set of tests. A fake AppDelegate may solve your problems.

Overview

Unit tests are an important part of software development and they must be fast and without side effects. Unfortunately, if you don’t pay attention, in iOS development you may risk side effects every time you run unit tests for your application. For this reason, a fake AppDelegate is a good way to enhance your tests.

In this article, I’ll explain why you should use it—using a simple example—and then I’ll explain how to create it. Happy reading!

Why Fake AppDelegate?

When iOS launches an app, it needs one of the following things:

@UIApplicationMain notation: it’s applied to a class to indicate that it is the application delegate.

UIApplicationMain() function: it’s called in the main entry point—which is usually main.swift—to create the application object, the application delegate and set up the event cycle.

By default, an iOS project has a class AppDelegate with the notation @UIApplicationMain. It means that this class is the entry point of your app.

Then, iOS has to find the entry point for the UI. We can use two different ways to load the main UI component: either Using Main Interface or Load Programmatically.

Using Main Interface

By default, an iOS project has a storyboard Main.storyboard where we have the main UIViewController of our app. By default, iOS searches the UI entry point inside this storyboard since it’s set in the Info.plist:

By default the initial view controller of this storyboard is ViewController, which becomes our main UI component.

With this approach, we often load the data of our application inside this view controller to use in the whole app:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

classUser{

letidentifier:Int

letname:String

init(identifier:Int,name:String){

self.identifier=identifier

self.name=name

}

}

classUsersProvider{

staticfuncfetchUsers()->[User]{

// Fetches from API

// Parses data and creates an array of users

}

}

classViewController: UIViewController{

private varusers:[User]?

overridefuncviewDidLoad(){

super.viewDidLoad()

users=UsersProvider.fetchUsers()

}

}

Load Programmatically

On the other hand, we can load the main UI component programmatically in the AppDelegate:

That’s all, you had a brief explanation of what happens when an app is launched. But you may still be wondering why you should care of these things.

When you run the unit tests, your app is launched in the target device selected (Simulator/Real Device) to load the app to test. It’s the same process which occurs when you launch the app to debug it. There are no differences. This means that, when you run the unit tests, the AppDelegate and the main UI component are loaded as usual to run the app. If you check the example used above, you can notice that the AppDelegate and the main UI component have the fetch of users as business logic. Therefore, the fetch of users is called every time we run the unit tests.

At this point, you may be thinking that it’s not a big problem. It can be a problem if at the startup of your application you read/write in a database, send API requests to insert/edit entities or compute heavy computations which may be time-consuming, slowing down the tests. For this reason, we need a way to skip these behaviours when you run unit tests to avoid side effects. A solution is a fake AppDelegate.

Fake AppDelegate

Getting Started

Before starting, if you are loading the main interface from Info.plist—like I shown previously—you have to remove it and load the main storyboard programmatically:

Create A New App Entry Point

First of all, we need a new entry point to load either the normal or the fake AppDelegate depending on whether the app is launched by unit tests or not.

As said at the beginning of this article, the entry point of an app can be either an AppDelegate with the notation @UIApplicationMain or the UIApplicationMain() function. Now, we need the later.

The first step is creating a new file main.swift. In this file we have to check if the app is launched by unit tests:

1

2

letisRunningTests=NSClassFromString("XCTestCase")!=nil

Then, we have to decide which AppDelegate class to load. For the class to use when the app is launched by unit tests, we have two choices: we can use either a FakeAppDelegate class where we can add the test logic to run before the tests—I’ll explain it better in “Create FakeAppDelegate”:

Or you can merely return nil. In this way, when you run the tests you don’t load any AppDelegate class. It would be the fastest and recommended solution if you don’t have to add behaviours in the FakeAppDelegate:

Create FakeAppDelegate

Here we are, you have just created the file main.swift and you decided that you want a FakeAppDelegate class. The point is, why do we want a FakeAppDelegate?

We know that the FakeAppDelegate is called just once and before the unit tests. It means that you have the possibility to run test logic in your FakeAppDelegate once and before running the set of unit tests.

Let’s look at an example:

Suppose that we want to write in a file every time we run the unit tests. We can start creating a new class called FakeAppDelegate.swift, and in its constructor we call the method to write the log message in a file:

Remember to extend NSObject otherwise the function UIApplicationMain in main.swift won’t be able to instantiate the class FakeAppDelegate.

Conclusion

I want to be honest with you. I haven’t started using a fake AppDelegate since my first day as iOS developer. A day I have started having side effects with my tests—because of the business logic inside my AppDelegate—and I had to find out a solution. This may mean two things: either I’m a bad developer—which is absolutely possible—or this topic is not so trivial, since the environment doesn’t help us a lot. In either cases, I hope you enjoyed this article reading something useful.

Share this:

Related

Marco Santarossa

Hi there, I'm Marco and I'm an Italian developer. I moved to London in 2016 to work at Sky as iOS developer.
I've been an iOS Developer since 2011 and I sometimes write embarrassing PHP/JS code.
I'm keen to learn new things and I spend most of my spare time learning as self-taught.
When I don't develop, I like watching MMA fights and cooking Italian food.

Actually, I think is right let appDelegateClass = isRunningTests ? NSStringFromClass(FakeAppDelegate.self) : NSStringFromClass(AppDelegate.self) because if isRunningTests is false I want to load the normal AppDelegate. Am I missing something?