Weekly Swift articles, podcasts and tips by John Sundell.

Simple Swift dependency injection with functions

Published on 18 Mar 2017

Dependency injection is a great technique for decoupling code and making it easier to test. The core idea is that rather than having our objects create their own dependencies by themselves, we inject them from the outside, enabling us to set them up differently for various situations — such as using mocks and other specific instances within our tests.

Most of the time, we use protocols to enable dependency injection in Swift. For example, let’s say we’re writing a simple card game, in which we’re use a Randomizer type to draw a random card from a deck — like this:

class CardGame {
    private let deck: Deck
    private let randomizer: Randomizer

    init(deck: Deck, randomizer: Randomizer = DefaultRandomizer()) {
        self.deck = deck
        self.randomizer = randomizer
    }

    func drawRandomCard() -> Card {
        let index = randomizer.randomNumber(in: 0..<deck.count)
        let card = deck[index]
        return card
    }
}

In the above example our Randomizer type is a protocol, which we inject an implementation of through CardGame’s initializer. We then use that object to generate a random index when drawing a card. In order to make our API easier to use, we also use a default argument — DefaultRandomizer — if no explicit randomizer is given. Here is what that protocol and default implementation might look like:

protocol Randomizer {
    func randomNumber(in range: Range<Int>) -> Int
}

class DefaultRandomizer: Randomizer {
    func randomNumber(in range: Range<Int>) -> Int {
        .random(in: range)
    }
}

While protocol-based dependency injection is great when our API requirements are a bit more complex, when we only have a single requirement, we can reduce our complexity by simply using a function instead.

We can see above that DefaultRandomizer is essentially just a wrapper around Int.random, so why not just simply pass a function of that type when doing dependency injection instead? Like this:

class CardGame {
    typealias Randomizer = (Range<Int>) -> Int

    private let deck: Deck
    private let randomizer: Randomizer

    init(deck: Deck, randomizer: @escaping Randomizer = Int.random) {
        self.deck = deck
        self.randomizer = randomizer
    }

    func drawRandomCard() -> Card {
        let index = randomizer(0..<deck.count)
        let card = deck[index]
        return card
    }
}

We have now replaced the Randomizer protocol with a simple typealias, and are passing the Int.random function directly as the default argument for randomizer. We no longer need a default implementation class, and we can still easily mock the randomizer within our tests:

class CardGameTests: XCTestCase {
    func testDrawingRandomCard() {
        var randomizationRange: Range<Int>?

        let deck = Deck(cards: [Card(value: .ace, suit: .spades)])

        let game = CardGame(deck: deck, randomizer: { range in
            // Capture the range to be able to assert on it later
            randomizationRange = range

            // Return a constant value to remove randomness from
            // our test, making it run consistently.
            return 0
        })

        XCTAssertEqual(randomizationRange, 0..<1)
        XCTAssertEqual(game.drawRandomCard(), Card(value: .ace, suit: .spades))
    }
}

I personally really like this technique as it lets me write less code, without sacrificing readability, and while still enabling dependency injection.

What do you think?

Feel free to reach out to me on Twitter if you have any questions, suggestions or feedback. I’d also love to hear from you if you have any topic that you’d like me to cover in an upcoming post.

Thanks for reading! 🚀