Designing and writing tests aren't the most exciting things in the world, we know, but tests are absolutely essential for any app you write. They might be the difference between a shining 5-star app on the AppStore, or a bug-ridden collection of code. We all know what Unit Tests are and how to write them, but since XCode 7, Apple has introduced us to UI tests inside their IDE. With these tests, you can record a user's interactions with your app and check to see whether it's behaving as it should.

In this article, we'll be focusing on the UI aspects of tests, as well as how you can record and write your first tests in an iOS app. But why focus on UI? Your app might do everything it needs to do under the hood, and your unit tests may have 100% coverage, but to a regular user, if anything seems out of place then there's an issue. Testing your app's UI lets you have a look at how the user would interact with it, and check to see if they're seeing exactly what they should be. Now, let's dive into how you can easily test your UI without any headaches.

Viper

When developing an iOS app, we must first decide what type of architecture we will use. Most developers use MVC, which Apple suggests, but MVC has its downsides. We've probably all read or heard the massive view controller joke somewhere. Sadly, in most cases it's true. When we have to put everything that doesn't correlate with View or Model's logic into the Controller's logic, there is a lot of code generated in the controller and not so much in the model or view. Let's take a quick look at what Viper is and its components. 

What is Viper

If you're not familiar with Viper, let's give a quick introduction. Viper is an acronym for View, Interactor, Presenter, Entity, and Router. It's basically an approach that implements the Single Responsibility Principle, aiming to create a cleaner and more modular structure. Viper achieves this by having a structure like this:


In the illustration above, we can see the Viper approach and how each piece of the architecture integrates with one another. Each element of Viper is responsible for doing one piece of work and asking/sending any other work to the responsible element. For example, the Presenter tells the View what it needs to show. The View's only job in this scenario is to show what the presenter has told it to show. In order to help testing, each element (except for Entity) of Viper implements a protocol so you can easily mimic any part of a class and write tests with zero headaches. Pretty simple, huh? Let's dive into more detail on each main component of Viper. 

View

View is passive because it basically just waits for the Presenter to give it content to display. It also receives inputs from the user and passes them on to the Presenter if anything needs to be processed. View also has all the logic on how to display data, such as which information goes to a `UILabel` and which `UIImageView` will be used to display an image that Presenter sent.
 
protocol ViewInterface {
func display(name: String, image: UIImage)
}

Interactor

We can think of the Interactor as a collection of use cases defined for a specific module inside your app. It contains all the logic related to data and should be completely independent of any UI logic, manipulating only Foundation objects.

Presenter asks Interactor what it should display, and it's the Interactor's job to fetch the data from an API, a database, or any other place your app's data might be stored. Once it gets this data, Interactor will have to transform it into an Entity and send it back to Presenter. No other work is done for this Entity and only the "transformation" from data to Entity is needed.
protocol InteractorInterface {
func fetchUser()
}

Entity

Entity is the simplest element inside Viper. It essentially just encapsulates different types of data, similar to the Model in the MVC method. If you have a struct called User, for instance, this User will be an entity.
struct User {
let name: String
let image: UIImage
}

Router

Router is the element which defines and handles all the routing logic of your app. It knows how to send your app to another page, and it also defines how this navigation will happen. Any Router of any specific module should implement all the possible paths for that specific module. This is good because it means you can have a quick look at the router and know where your app can go from there.
protocol RouterInterface {
func logout()
func goToAnotherPage()
}

UI tests with XCTest

Now that we introduced Viper and how modular it can be, we can start talking about UI tests and how you can write them using Viper. We won't be discussing which framework is better for developing UI tests in iOS, instead, we'll be focusing on the XCTest provided by Apple. The easiest way to start writing your UI tests is to record them. XCode provides a record button, that interacts with your app and automatically generates code that can be groomed if needed. To start this process, simply add a new target to your project and select the iOS UI Testing Bundle.
 
UI Testing Bundle.png
 
Input the name you want for your testing target and create it. XCode will add this new target to your project and automatically create a new file with the same name as the target. In this file, you'll have a few functions already created. There's setUp() and tearDown() which run before and after every test, and there's also a testExample(). You can delete them if you want, but for the sake of simplicity, you can just select function testExample() and start your testing. XCode will then allow you to record your tests through the red circle button at the bottom of the page.
 
Record Button.png
 
Click it and XCode will build your app and launch a simulator. Every action you take on this simulator will be recorded and written down on the function you selected. The page I created for this article has two elements: a UIImage and a UIButton. When I ran the recorder and clicked on both, it generated the following code:
 
        let app = XCUIApplication()
        let element = app.children(matching: .window).element(boundBy: 0).children(matching: .other).element.children(matching: .other).element.children(matching: .other).element.children(matching: .other).element
element.tap()
        let logoutButton = app.buttons["Logout"]
        logoutButton.tap()
 
Wow, that's a lot of code just for tapping on an image, isn't it? The UIButton is better since it has text and XCode knows how to search for it, which means you get what you're looking for. So, how can I do the same with an image? Well, there's an accessibilityLabel that can help you find any element on your screen. I set the image's acessibilityLabel to profileImage which makes your test code way easier to write.
let app = XCUIApplication()
app.launch()
let image = app.images["profileImage"]
XCTAssert(image.exists)

 notBad.jpg

The accessibility label is your best friend when developing an iOS app. It provides users who have hearing impairments with a way to navigate through your app and also helps you better test your UI.

What About Viper?

You must be asking yourself why I introduced Viper to you in the first place. The tests we did could be done with MVC, sure, but what if you have to test something that's deeper down in your app? Since Viper is so modular, you can write your classes thinking about tests, which make them extremely injectable. You can also create a class solely for instantiating all the elements of Viper, where you can inject anything you want. In our example, I created a builder called ProfileBuilder that instantiates all elements of Viper and returns the Router element.

final class ProfileBuilder {
static func build(withUser user: User) -> ProfileRouter {
        let storyboard = UIStoryboard(name: "Profile", bundle: nil)
        if let viewController = storyboard.instantiateInitialViewController() as? ProfileViewController {
            let interactor = ProfileInteractor()
            let presenter = ProfilePresenter(viewInterface: viewController, interactor: interactor)
            let router = ProfileRouter(viewController: viewController, presenter: presenter)
            viewController.presenter = presenter
            interactor.presenter = presenter
            presenter.router = router
            presenter.user = user
            return router
        }
        fatalError("Oh no, something went wrong")
    }
}
Check out what happens when I inject a User when building my class . Voila, now you have a Router as well as a reference to your View. With that reference, it's easy to show it anywhere you want. If you want to just show it when the app starts, go to your appDelegate and just make your View the rootViewController.
let user = User(name: "Test", image: UIImage(named: "profileImage"))
let profileRouter = ProfileBuilder.build(withUser: user)
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = profileRouter.viewController
window?.makeKeyAndVisible()
 But if I add this code to appDelegate, won't the Profile page appear every time?
 
 
Yes, you're right. And that's why you can pass arguments when launching an app as well as check these arguments in your appDelegate. Here's what my code looks like in my test and in the appDelegate:
let app = XCUIApplication()
app.launchArguments = ["UITEST"]
app.launch()
let image = app.images["profileImage"]
XCTAssert(image.exists)
let args = ProcessInfo.processInfo.arguments
if args.contains("ProfileUITest") {
let user = User(name: "Test", image: UIImage(named: "profileImage"))
let loginRouter = LoginBuilder.build(withUser: user)
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = loginRouter.viewController
window?.makeKeyAndVisible()
} else {... instantiate another page...}

 
You can and should improve your code to make it look better than this. You can pass as many arguments as you need and check for each one, or all of them, in your app delegate. Since this argument ProfileUIITest is only passed when running UI tests on your profile, that specific page will only be called then.

Wrapping Up

You now have some initial insight on how to make UI tests without getting any headaches. It's easy to design them once you get used to using Viper in your project, which might take some time, sure, but it'll be worthwhile. I promise. To speed things up, you can check some articles specific to Viper architecture here, here, and here. Also, you can take a look at some articles on UI tests specifically and Apple also provides some insight here. Although it's not entirely focused on UI tests, this tutorial is a good way to learn a few new things as well as understand why UI tests are important. 
 

Author

Jean Sandrin

I’m passionate about iOS development, currently enjoying building apps with VIPER architecture.


Try Dev Box Testing And See How Much Time You Can Save

READ MORE

How To Develop An Automation Framework - Part 1

READ MORE

How to Structure Visual Regression Testing Based On Business Needs

READ MORE