iOS

Advanced Unit Testing in Swift Using Swinject, Quick and Nimble


Do you want to build a car (Testable Architecture) that can run fast (less compile time), save petrol (repeated code) and can be easily learnt (readable) & driven (picked up) by any licensed driver? Then you have come to the right place. In this tutorial, I am going to take you on a journey to build your Swift-brand car, using Swift 4.2.

swift-brand car

Building a Testable Architecture

As we advance to another level of our developer’s journey, we will come to a point where we want to write more testable & reusable code. Code that can be easily understood without writing comments, and can be easily picked up by another developer.

It is merely impossible to introduce one size fits all architecture. But here, I am going to introduce one way of developing your project, mainly for medium-to-large applications, that can get you started in achieving 80% of what I just mentioned. I do not guarantee that this is the best way of developing, but I can say that it helps us achieve what this tutorial is written for; which is to go deeper into writing testable code.

Frameworks

I am going to assume that you already have understanding of how to use Xcode, Cocoapods and develop code in Swift. I will introduce here all the frameworks that we will be using to learn and develop our app (I will state the version I use as they may not work as it is forever):

  1. Quick 1.3.2 & Nimble 7.3.1 – The Swift (and Objective-C) testing framework.
  2. Swinject 2.5.0 – A lightweight dependency injection framework for Swift.
  3. ObjectMapper 3.3.0 – A framework to easily map model objects from JSON.
  4. Alamofire 4.7.3 – A HTTP networking library written in Swift.
  5. AlamofireObjectMapper 5.1.0 – Works together with ObjectMapper & Alamofire
  6. RealmSwift 3.11.0 – Database built for Mobile.

You can grab a copy of the Podfile here as well as the Podfile.lock here to ease your trouble.

Editor’s Note: If you are new to Quick and Nimble, please first check out our tutorial on these frameworks.

The Architecture

We have now come to the main ingredient of this tutorial, the body of the car, the engine, the wheels and the car seats which help build this archiecture.

  1. MVVM (Model View ViewModel) Design Pattern: This will be the design pattern which we will be following. It is a trending iOS architecture that focuses on the separation of development of user interface from development of the business logic. The ViewModel acts like a glue to bind Model and View. The main component we will be performing our unit tests is the ViewModel.
  2. Protocol Oriented Programming: Swift language is powered by protocols. They provide developers with the freedom of creating blueprints of classes, exposing only the right capabilities for other classes to use. The biggest plus point here is that capabilities make the codebase readable if they are properly defined.
  3. Dependency Injection (DI): James Shore says that “Dependency injection means giving an object its instance variables. Really. That’s it.” Dependency injection is nothing more than injecting dependencies into an object instead of tasking the object with the responsibility of creating its dependencies. This gives us the flexibility of injecting objects to implement our test cases.
Editor’s note: If you are unfamiliar with MVVM and Protocol Oriented Programming, please do check out our introductory tutorial before moving on.

Pre-requisite

After understanding all these frameworks and terminologies that we will be using to go through this tutorial, it’s time to get started! Clone or download this Starter Project and we are ready to go! You will need:

  1. macOS 10.14 & above
  2. Xcode 10 & above
  3. Cocoapods 1.5.3 & above

Once you have got your starter project, run pod install to ensure all frameworks are properly installed.

You may have noticed that there is a testing_pods in our PodFile, that is intentionally defined to ensure testing pods are only installed for our tests target.

People Roulette

Today we will be building a very simple People Roulette App as we go through this tutorial. This app serves like a simple random generator to randomly select people. For example, when everyone is tired and resting in the hotel, but you need one guy to get some beer for a night party and no one wants to go, you could use this app to pick one unlucky guy. 😛

unit testing_App Home Page
unit testing_App List
unit testing_App Details

Project Skeleon

Open the project with Xcode and you should see a project structure as such:

  1. Model -> User
  2. Utilities
    • DependencyAssembly.swift (Dependency Injections)
    • Constants (Constant values)
    • Handlers (RealmDB & Roulette functions)
    • Network (API/Network relation functions)
    • Services (Helper Functions)
  3. Extensions (Some extension of existing capabilities)
  4. Features (Components of the App)
  5. Resources (Storyboard, Assets, Info.plist)

This project structure helps us to find a specific file faster (i.e. when working on Roulette feature, just go to features and jump in to the related files).

Implementing UsersHandler (Building the Rolulette)

First and foremost, we will need a list of users. Instead of hardcoding them, we are going to get it from an open public API from here.

Let’s think MVVM

The View will notify its ViewModel to pull data using Handler. After the Handler receives data, it will update the Model. The Model will then eventually be retrieved and updated back to ViewModel and View, which will end the UI update.

For simplicity, the User Model class has already been completed for you. Let’s head over to UsersHandler class and start working from here. We have 2 protocols:

  1. UsersDownloading helps us to get users data from the server and save it into our RealmDB.
  2. UsersRetrieving helps us to get users data from the RealmDB.

We will complete getUsers function by implementing this:

  1. First we created an APIRequest struct, give it a URL and method.
  2. Then we pass it to apiHandler which leverage on Alamofire to make the HTTP call.
  3. Once it gets response, the guard statements will handle any error response and the ObjectMapper will map JSON response data to User Models.
  4. We will then flush over any existing data in RealmDB.
  5. Then save the latest data.
  6. Since it’s an @escaping completion block function, we will need to pass it some completion values to exist the function.

Now let’s complete the handler class by implementing the rest:

  1. purgeUsers uses RealmDB‘s deleteObjects function to delete all users data.
  2. saveUsers uses RealmDB‘s saveObjects function to save all users data.
  3. loadUsers uses RealmDB‘s getObjects function to retreive all users data.

We will not talk more about RealmDB here. For more info, you could read their docs here.

Unit Testing – UsersHandler

Here, we are going to write our first test for making a network call to Get Users. Open UsersHandlerSpec.swift and we are going to start writing our test implementation in spec(). For more info about using Quick & Nimble, please go here.

Since it’s network related, there might be failed server or unstable network. We would not want to test the real network call. Therefore, we are going to leverage on the powerful method of Mock. By Mocking, we can control cases such that the:

  1. The network call always succeed/fail.
  2. The network call always returns the correct/incorrect data.

This way, we can write test cases around what happened in each case.

So go ahead and add this bunch of test code:

  1. We first load in the appropriate mock classes from our RealmHandler but we are not testing anything related to DB. This is essential as our getUsers method users their functions and test will crash if no object is mapped to them.
  2. We then write 2 suite of test cases (success & failure).
  3. Here we call the same api function but we test against the function to ensure that it handles the response correctly.

Wow! That’s a huge amount of effort put in just to create a method for a network call! Yes, it does, but hard work will pay off. With components more loosely coupled, you can better split up the workload, do pair programming and write more robust code.

So we are now going to move up one level, to use UsersHandler in our PeopleRouletteViewModel.

Implementing PeopleRouletteViewModel

Remember that ViewModel retrieves data from Model and prepare them to be used in View. Go ahead and put in these code:

  1. Using our UsersRetriever protocol, we loadUsers from our RealmDB after getUsers successfully retrieve users from the server.
  2. Here we are using maxCount and minCount to determine the lower and upper bound of our UIPicker.
  3. This function is similar to UsersHandler, except that we no longer pass users to View, we only inform the View if the call is successful.

Unit Testing – PeopleRouletteViewModel

Open up PeopleRouletteViewModelSpec.swift and implement these code:

  1. Here we are testing the scenario when getUsers succeeds.
  2. Here we are testing the scenario when getUsers fails.
  3. Here we are testing that the right data is prepared and accurate.
  4. Again, we are leveraging on the power of Mocking to simulate data from API Call.
  5. Again, we are leveraging on the power of Mocking to simulate data from RealmDB.

Now run the test by using CMD+U or Product -> Test and you should see all your test cases passed!

Implementing PeopleRouletteViewController

Give yourself a big pat on your back! We are almost there, what we have just did is actually the most tedious part of the tutorial. I encourage you to repeat the previous steps by re-downloading the starter project and do it without checking this tutorial.

Now we are going to display the prepared data to users on the app. Add these missing code which we just implemented in the ViewModel layer to use it now in the View:

When the network is making the network call, show spinner until it responded.

Once the ViewModel has the result, we can use it to determine the number of rows, which is equivalent to the number of people. Transform each Int value to String to display it in our picker view. Then set in to our textField once it’s selected.

This power up our app to retrieve users list from server, and choose the number of people based on its data.

Now run the app and …. OOPS! Did the app just crash?

Yes it did, that’s because peopleRouletteViewModel is being forced unwrapped but it doesn’t point to any object yet. Here is where we will introduce Dependency Injection. Instead of initialising PeopleRouletteViewModel here, we will use Swinject to help us inject all our dependencies.

Head over to DependencyAssembly.swift and update these:

and head over to registerViewModels to update PeopleRouletteViewModel with

Yeah! We are now good to go. Just like a car needing a key to start the engine, all these dependencies help us to kick start the car.

Run the app now and you can choose number of people!

unit testing_App Home Page

Implementing RouletteHandler

I am going to go a little faster from here, as it will be the same ritual.

  1. Implement Handler (Capability)
  2. Implement View Model (Prepare Data with Capability)
  3. Implement View (Use Data)
  4. Implement Unit Tests

Open up RouletteHandler.swift and implement the following:

  1. The capability of this class is to apply Roulette base on the users list and number of people selected.
  2. The logic here is to leverage on Darwin to generate unique random users.

That’s all, you are ready to use your roulette! But before we jump right in, let’s unit test this handler so as to “lock down” its logic.

Unit Testing – RouletteHandler

Head over to RouletteHandlerSpec.swift and implement these:

  1. We need to test that our handler supports generating one random user.
  2. We need to test that it also supports more than 1 user.
  3. We need to test that all values are unique by testing against getting all users.

Implementing Navigation

Before we go on to use our PeopleRouletting, we need to navigate user to the next screen where the Roulette and the list of lucky winners displayed.

Let’s head back to PeopleRouletteViewController and add this line:

ViewControllerInjector is already existed in the codebase. Its main job is to do all the heavy work of injecting viewControllers for you. Don’t forget to head over to Dependency Assembly and inject in controller.viewControllerInjector = resolver.resolve(ViewControllerInjecting.self) too.

Head back to the viewController and add UsersListViewController just above numberOfPeople declaration:

We perform a lazy load of the viewController here to ensure injection only happens once. In such case, viewDidLoad will only be called once as well.

Now implement our roulette action:

Are you ready to navigate? Run Test and then run the app to ensure everything is in tact before moving on!

You should be able to select number of people, click on the Roulette Wheel and navigate to next screen. But nothing is showing yet.

Implementing UsersListViewModel

This is where we will use PeopleRoulette to populate our list view.

Inject PeopleRouletting in. Once you are done, use it in setup like this:

Very simply, we randomly pick out the selectedPeople objects, and then prepare one cellViewModel per object.

Unit Testing – UsersListViewModel

Head over to UsersListViewModelSpec and fill them up:

Here we provide the PeopleRoulette object with some data, then test that with data available, it should:

  1. Calculate the correct number of rows.
  2. Get the correct user data from each row.

Implementing UsersListItemCellViewModel

Head over to UsersListItemCellViewModel to implement our code:

In cellViewModel, we only want neccessary data to be shown to user. Therefore, we should not load the entire model.

Unit Testing – UsersListItemCellViewModel

Given a cellViewModel, test that it populates the right data.

Implementing UsersListViewController

Now fill up the tableView methods in the viewController:

  1. First, we use the viewModel to render the correct number of rows.
  2. Then, we get the cellViewModel of the row, and render its cell.
  3. Cell creation is happening here, where the cellViewModel is used to map data to cell.

Running the app now will crash the app as it seems like a DI is missing. Can you resolve it?

Now run the test and the app, choose people and you should see a list of selected unique people! Hurray!

unit testing_App List

Implementing UserDetailsViewModel

The last step is to allow user to tap on each person’s details to view them. Go ahead and implement this view model:

Unit Testing – UserDetailsViewModel

In UserDetailsViewModelSpec:

We are testing if the details of the user are displayed correctly and in the right format here.

Implementing Navigation

Challenge yourself now. Add in viewControllerInjector and inject in UserDetailsViewController using lazy loading. Then, implement this in tableView delegate to navigate to the details page:

Implementing UserDetailsViewController

Populate setupUserInfo function and we are almost there!

Again, the app crashes because of missing dependencies. Go ahead and solve them! Now run the test & the app and you should be able to view user details with 18 test cases passed!

unit testing_App Details

You are a Champion!

This is one of the toughest tutorials I have ever written and please provide your feedback so I can be better at it. I have mainly touched on the advanced methods of coding in swift.

  1. Using MVVM Design Pattern
  2. Using Dependency Injection
  3. Using Protocol Oriented Programming
  4. Unit Testing

In the real world, it is not ideal to test every single code though we always try our best to achieve 100% test coverage. However, we can always pick out the core components of the app to test (i.e. Server & Roulette in our case) so as to ensure that we have “Locked Down” its features. When it comes to code review and KT (Knowledge Transfer), test cases (if written well, i.e. BDD way) can help in understanding the codebase faster.

For coming this far and understanding these concepts, you deserve a cookie!

You can download the complete project here.

Tutorial
How to Use Xcode Targets to Manage Development and Production Builds
iOS
Building an RSS Reader Using UISplitViewController and UIPopoverViewController
iOS
Creating an Immersive User Experience with Haptic Feedback in iOS
Shares