Unit Testing in Swift: Dependency Injection

One of the harder topics for me to understand in testing is definitely dependency injection. However, after writing a bunch of tests last year (hello 2015!), I think I finally have a handle on it, and would like to share what I learned.

I’m going to use a very basic Minions app as an example. The app just shows a tableView of minion names and images. To get set up, I have a few basic objects set to go:

The Setup

The Minion struct holds information about each Minion. To make it simple, each Minion has a unique name, so two Minions with the same are actually the same Minion (as specified by the Equatable protocol extension):

//  Minion.swift

struct Minion {
    let name: String
}

extension Minion: Equatable { }
func == (lhs: Minion, rhs: Minion) -> Bool {
    return lhs.name == rhs.name
}

The MinionService would be used to make an API call to get the Minion data in the real world (using AFNetworking or Alamofire). To keep it simple, let’s pretend this is done asynchronously… For this example, it just returns two hard-coded Minions, since this is not the main part of this demo:

//  MinionService.swift

class MinionService {
    
    func getTheMinions(completionHandler: ([Minion]) -> Void) {
        println("getting minions asynchronously")
        let result = [Minion(name: "Bob"), Minion(name: "Dave")]
        completionHandler(result)
    }
    
}

Deciding What To Test

So now let’s get to the fun part! Here is the initial version of the ViewController:

//  ViewController.swift

import UIKit

class ViewController: UITableViewController {

    var dataSource: [Minion]?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        fetchMinions()
    }
    
    func fetchMinions() {
        let minionService = MinionService()
        minionService.getTheMinions { [unowned self](minions) -> Void in
            println("Show all the minions!")
            self.dataSource = minions
            self.tableView.reloadData()
        }
    }
    
    // MARK: UITableViewDataSource
    
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataSource?.count ?? 0
    }
    
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        
        let cell = tableView.dequeueReusableCellWithIdentifier("minionCell") as UITableViewCell
        
        if let minion = dataSource?[indexPath.row] {
            cell.textLabel?.text = minion.name
            cell.imageView?.image = UIImage(named: minion.name)
        }
        
        return cell
    }

}

The part I want to focus on testing is the fetchMinions method. I want to make sure that:

  1. fetchMinions calls the MinionService instead of just generating some random array of Minions
  2. fetchMinions assigns the resulting array of Minions to the data source. In your own ViewController, you might want to test other side effects if there are any.

Dependency Injection

The way to test the two things mentioned above, we need to dependency inject our own MinionService. There are several ways to do this, but in this case I’m going to use Method Injection – just pass a MinionService to the fetchMinions method, using a default value (yay Swift!), so I don’t actually have to pass anything else if I don’t want to (e.g. the way it is called in viewDidLoad() will not change!):

class ViewController: UITableViewController {

    var dataSource: [Minion]?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        fetchMinions()
    }
    
    func fetchMinions(minionService: MinionService = MinionService()) {
        minionService.getTheMinions { [unowned self](minions) -> Void in
            println("Show all the minions!")
            self.dataSource = minions
            self.tableView.reloadData()
        }
    }
  
    // truncated code

}

Write the Tests

The first step is getting a reference to the viewController instance. Since my viewController loads from the Storyboard, I want to make sure to also load it via Storyboard, so it’s set up just like it would be in the real world. This is how you do that in Swift:

//  ViewControllerTests.swift

import UIKit
import Foundation
import XCTest

class ViewControllerTests: XCTestCase {
    
    var viewController: ViewController!
    
    override func setUp() {
        let storyboard = UIStoryboard(name: "Main",
            bundle: NSBundle(forClass: self.dynamicType))
        let navigationController = storyboard.instantiateInitialViewController() as UINavigationController
        viewController = navigationController.topViewController as ViewController
        
        UIApplication.sharedApplication().keyWindow!.rootViewController = viewController
        let _ = viewController.view
    }

The next step is to create our own MinionService that we can control. This is the hardest part of dependency injection! The nice thing is that our Fake MinionService can be a subclass of MinionService, so we can override the method we’re testing (and if that method signature changes, our code won’t compile!).

Again, we want to make sure that our MinionService gets called when the fetchMinions method is called in the ViewController, so we’ll have to add a getTheMinionsWasCalled variable to our Fake MinionService to track this.

We also need to add a result variable to our Fake MinionService so that our test can control the result passed back to the ViewController. In summary, dependency injection lets us have very fine-grained control of what information is passed and used by the viewController.

//  ViewControllerTests.swift

class ViewControllerTests: XCTestCase {
    
    class Fake_MinionService: MinionService {
        var getTheMinionsWasCalled = false
        var result = [Minion(name: "Bob"), Minion(name: "Dave")]
        
        override func getTheMinions(completionHandler: ([Minion]) -> Void) {
            getTheMinionsWasCalled = true
            completionHandler(result)
        }
    }
    
    // truncated code

}

Now, this is the easy part. Just write the test!

//  ViewControllerTests.swift

class ViewControllerTests: XCTestCase {

    // truncated code

    func test_fetchMinions() {
        let fakeMinionService = Fake_MinionService()
        
        viewController.fetchMinions(minionService: fakeMinionService)
        
        // Test that the MinionService was actually called
        XCTAssertTrue(fakeMinionService.getTheMinionsWasCalled)
        
        // Test that our injected result was assigned
        // to the dataSource (the side effect)
        if let dataSource = viewController.dataSource {
            XCTAssertEqual(fakeMinionService.result, dataSource)
        } else {
            XCTFail("Data Source should not be nil!!!")
        }
    }
}

You can view the full sourcecode on Github here. Happy testing!

Enjoy the article? Join over 14,500+ Swift developers and enthusiasts who get my weekly updates.

  • Marian

    I guess you wanted to write:
    XCTAssertEqual(fakeMinionService.result, dataSource)

    • No, the way I have it is correct. One of the side effects of my fetchMinions function is that it assigns the result from the service call to the dataSource on the viewController, so that’s why I’m asserting that the result from the minion service is equivalent to the dataSource in the viewController.

      • Noodle

        Isn’t

        XCTAssertEqual(fakeMinionService.result, dataSource)

        more or less the same as

        if let dataSource = viewController.dataSource {
        XCTAssertEqual(fakeMinionService.result, viewController.dataSource!)

        }

        anyways?

        • Looked and the code again, and now I get what you’re saying. Unintended mistake on my part – updating the post.

  • Raphael Oliveira

    I have a question Natasha, what if you want to test a UIAlertView? For example, you want to test that if something happens then a UIAlertView appears with a specific message. Usually you would have create an UIAlertView inline? Would you do dependency injection for this UIAlertView? Can you give an example please?

  • Considering that the minionService fetchMinions is async, testing it like datasource is assigned synchronously is confusing for me. won’t the test involve the completionHandler?

  • Maxime Capelle

    Thanks to your article, I had a massive use of subclassing in order to mock various objects for unit testing.
    In your example, if an object had a property of ‘Minion’ type, I would subclass the ‘Minion’ class into a ‘FakeMinion’ to isolate my tests.

    The problem is that I have to use classes instead of structs to be able to subclass my objects.
    Because Apple recommends to use value types with structs, I would like to know if you have worked on a pattern to do dependency injection with Struct? Maybe protocols ?
    Thanks.

    • Johnathan Raymond

      I just finished writing a protocol based dependency injection framework for Swift 2.0 – maybe give it a try? https://github.com/knightswhosaynil/Apodidae

      • Maxime Capelle

        Thanks, I’ll take a look to it.

  • Brian Keane

    Just wanted to say thanks a TON for this — it is by far the clearest intro to depency injection I found and it was very easy to adapt it to my own app. Thanks again

  • Richard Brown

    We’ve been looking into dependency injection, and this was a help. However, I kept seeing the regular app called when testing. I know that’s not clear, so, if you get your latest project, upgrade it to XCode 7.3+, and place breakpoints within both the real MinionService and the Fake getMinions function, and run just the test_fetchMinions. You see that both breakpoints are called. Some searching on (where else) Stack Overflow found this: http://stackoverflow.com/questions/15714697/unit-testing-in-xcode-does-it-run-the-app . I tried using Tomasz Bąk’s solution in your code, with some necessary changes and it seems to work. Thoughts?