iOS Unit Tests – My story

Reading Time: 5 minutes

In my last job interview – almost 2 years ago – I received a question about writing unit tests inside the iOS code. With the confidence of a developer with 8 years of experience in the iOS branch, my answer and my opinion at that moment were that if the developers are high-profiled and write good code writing unit tests is not necessary. Also, my answer continued with a conclusion: if the company has a testing team why do we (iOS developers) take their job? Throwing back on the answer and from today’s perspective, my feelings about the topic are mixed: First of all, I’ve learned more about the importance of the unit tests, and secondly, I’ve continued to work in an environment where we are not obligated to write tests.

Some basics

We should consider our code as pieces of code – called UNITS. The purpose of a unit test is to make validation of our code, and this will allow us to be sure that our code meets the design and fulfil the goal.

In XCode and iOS, the Unit tests are written in a separate target. The most important thing is the XCTest framework. The basic rule is that only the methods that start with the word test will be considered as unit tests by the compiler. Here is an example:

func testFormatForCard() {
    let formatter = DateFormatter()
    formatter.dateFormat = “ddMMyyyy”
    let date = formatter.date(from: “28061978")!
    XCTAssertEqual(date.formatForCard(), “28.06.1978”)
}

Once a test method is written with the proper semantic, the method is associated with rhombus sign on the left side:

Unit Tests with rhombus sign

We can run the tests to see if our code is good or if something is not working. If the test passes, the empty rhombus sign is changed with a green checkmark sign:

Figure 2: Test passed

In case of a failing test we have an assertion and a red sign:

Figure 3: Failed unit test

We can see in image 3 that intentionally the programmer entered the wrong output date 29.06.1978. That’s the way we should think when writing unit tests. First, we have to write the failing state and after that, we should enter the expected correct output. If in the second case the test passes, then we have created a useful unit test for that piece of code (unit). The general idea behind is if we change something in our code, and we made a mistake unintentionally the test should fail and warn us to fix the code.

Unit Test in practice

1. Code Coverage

There is a built-in option inside XCode for checking the code coverage of the tests. The ideal scenario is that 100% of our code is covered by unit tests. But is this really necessary? Will we be better programmers if our full code is supported with tests? Can we spend better the time we spent covering the tiniest pieces of code with proper tests?

In my opinion, writing unit tests is a philosophy and knowing the principles lead us to write qualitative code. That’s, of course, our goal as programmers. Covering the most important part of the code, especially the parts of code that are often changed like networking managers, parsers are a better option than trying to be perfect and writing 100% coverage.

2. Test Driven Development

The popular blog website for programmers Ray Wenderlich emphasizes the FIRST principles as a good way to follow writing unit tests. The basics of these principles are that the test should be fast, autonomous, repeatable, the output should be either “pass“ or “fail”, and ideally, the tests should be written before coding – Test Driven Development TDD.

The recommendation by TDD is writing tests before starting to fix a bug in the code. My opinion on this topic is also a mixing approach. Depending on the time you have, if the deadline is not close on the horizon, you can write tests before coding, or before starting to fix bugs in code, but that’s not always possible, and you won’t make a mistake if you skip this step sometimes.

Conclusion

I can say that writing unit tests is good for every programmer on his way to becoming great. The quality of coding could dramatically improve using unit tests. The philosophy of writing tests and thinking how the code should be structured to allow the tests to pass, will make you write cleaner code, better structured, using more protocols, make reusable classes… As in the strategy games, you shouldn’t always be a slave of the principles – the most important is to fulfill the goals in a quality manner. If you have enough time and not a strict deadline of the project you can make a bigger code coverage, but something around 75% is good enough.

Making Swift networking code more readable

Reading Time: 3 minutes

With Swift 5 a new type got introduced:

@frozen public enum Result<Success, Failure> where Failure : Error {

    /// A success, storing a `Success` value.
    case success(Success)

    /// A failure, storing a `Failure` value.
    case failure(Failure)

The Result type is an enum consisting of 2 cases. The success and the failure case. Each of them can hold a generic value. The failure case, however, is limited to Types extending the Error type.

Not a big deal? Sure, but it’s the little things which add up and make a difference in the long run.

Lately, I was migrating from SwiftyJSON to native JSON parsing. Each network call was implemented in the following way:

func fetchSomething(completion: @escaping (SomeReturnValue?, SomeError?) -> Void) {
    NetworkingTool.request { (response) in
        guard response.isValid
            else { completion(nil, .somethingBad); return }
        do {
            let returnValue = try SomeReturnValue(response: response)
            completion(returnValue, nil)
        } catch {
            completion(nil, .scarry)
        }
    }
}

Looks okayish. Good. So let’s use it:

fetchSomething { (result, error) in
    guard error == nil
        else { handleError(error: error); return }
    doSomething(result: result)
}

Ok. But how to implement the doSomething? With an optional? This can’t be right, right? Force unwrap the result? And what about the error case? Force unwrap it? Oh and wait, what about the case where neither a result nor an error is returned? Is this even a thing? Ok, let me look up the implementation…

So a tiny bit of ambiguity paired with different people working on different parts of the network stack for different features can cause a real heterogeneous system. (Which does not imply that this is a bad system!)

If the company you’re working for is in favour of code ownership, you may not encounter this one. But so far no company I worked for was about code ownership. It’s usually your code is my code is our code, comrade. Period. There are simply too many trucks outside.

As long as code ownership isn’t a thing and you do not want to spend time on endless syntax and architectural discussions with little benefit or enforce a (new) best practice on all of your colleagues. Again. It comes really handy to have a built-in Result type which is reasonably unambiguous.

And since we all know that we’re spending more time reading code than writing, this saves us all valuable time.

Dependency injection and how I use it in Vaccination iOS app

Reading Time: 5 minutes

In programming, dependency injection is a technique where one object serves dependencies to another object. The concept is that instead of the client object to decide what kind of service it will use, another object tells to the client what service he has to use.

We can see the dependency injection as a software pattern. The fundament of this pattern is passing the service or object to the client, instead of allowing the client to find or to build the service on his own. What is the advantage of using this pattern? The main pros of this pattern are the readability of the code and code reusability.

Dependency injection – Injector example

Dependency injection is one form of the broader technique of inversion of control. The client delegates the responsibility of providing dependencies to external code (the injector) (Figure 1). The client is not allowed to call the injector code; it is injecting code that constructs the services and calls the client to inject them. This means the client code does not need to know about the injecting code, how to construct the services or even which actual services it is using; the client only needs to know about the intrinsic interfaces of the services because these define how the client may use the services. This separates the responsibilities of use and construction.

Types of DI

There are 3 types of dependency injection:

  1. Constructor injection: the dependencies are provided through a class constructor
  2. Setter injection: the client exposes a setter method that the injector uses to inject the dependency
  3. Interface injection: the dependency provides an injector method that will inject the dependency into any client passed to it. Clients must implement an interface that exposes a setter method that accepts the dependency

Vaccination app example

In the iOS world, the constructor injection is known as an initializer-based injection. This concept is realized with injecting the dependency object (or service) during initialization of the client class and this dependency is consistent/unchangeable during the life cycle of the client object.

In the previous few months, I’ve worked on the vaccination iOS application for N47 and I’ve decided to use the popular MVVM pattern inside. In the core of this pattern is the dependency injection. The components of the pattern are Model, View, and ViewModel, and each component is responsible for a different thing in the app. The point is to make the code more modular and easy to test.

The ViewModel (VM) component is a structure that contains only the data needed by the View component. The View component is presenting the data injected by the ViewModel. The ViewModel at other side is created by injecting dependency from the Model component. The main advantage of the MVVM is that we are creating views that have only one goal – presenting data. The view itself is not aware of the other task like fetching, persisting, etc.

We can see the initializer-based injection in action with the real example used in the Vaccination Demo App of N47. Let’s see first how the Details ViewModel looks like:

struct VaccineDetailsViewModel {
    let title: String
    let description: String
    let date: String?
}

The vaccine details view only needs title, description, and date for the vaccine. It doesn’t need more information. On the other hand, the vaccine model can contain more details about the vaccine, but this information is useless for the View. Inside the view controller (View component) we define view model property and set it via controller initializer. We can see this in the code snippet below:

 var vacineViewModel: VaccineDetailsViewModel?

class func createController(viewModel: VaccineDetailsViewModel?) -> VaccinesDetailViewController {
     
        let controller = VaccinesDetailViewController(nibName: "VaccinesDetailViewController", bundle: nil)
        controller.vacineViewModel = viewModel

        return controller
    }

This type of injection is preferable because it keeps us the safety of creating incomplete objects and with that, we will avoid coding mistakes.
So when I want to create a controller that will present the details for the vaccines and the scheduled vaccines I’m using injection via initializer in this way:

let details = VaccinesDetailViewController.createController(model: vaccination.createModel())

Other DI types in action…

In the Vaccination App, I’m also using Dependency Injection via setter creating the UITableView cells.

var vaccinationData: Vaccination? = nil {
        didSet {
            guard let vaccineId = vaccinationData?.vaccineId else { return }
            guard let vaccine = VaccineManager.sharedInstance.getVaccineById(vaccineId: vaccineId) else { return }
            let language = ModuleSharedPreferences.shared.language.rawValue
            let translation = vaccine.translations[language]
            
            vaccineTitleLabel.text = translation?.name
            vaccineApplyDateLabel.text = vaccinationData?.date
        }
    }

The code snippet above shows the vaccination data object that should be set with setter if we want the cell to be populated with data. Here is the code that will do the magic:

        let cell = tableView.dequeueReusableCell(withIdentifier: VaccinesTableViewCell.cellIdentifier, for: indexPath) as! VaccinesTableViewCell
        
        let vaccination = vaccinationList[indexPath.row]
        
        cell.vaccinationData = vaccination

Conclusion

Dependency injection is a powerful technique. Our code becomes more readable, reusable and easy for testing. We were able to see this technique in action in a real project and it was used within the popular design patterns MVVM. Using this technique we become sure that our components/services are completed, fully created before we start to use it.