Swift: Dependency Injection with a Custom Initializer

As my first project of the year, I’m working on the Swift version of @catehstn’s iOS Unit Testing Workshop – you can sign up here if you’re interested.

As I was working through my code and writing tests, I ran into the following problem…

Let’s say I’m building a simple counter in Swift:

This is super simple, and I can easily write a test for increment:

This is great, but what about about this case!

Since the Counter is initiated with the count of 0, our multiplication test is not as effective:

To be an effective test, we need to test our scaleBy function when the count is not 0 – we need to have control over the initial counter value. This is where we can use default values to inject the init function with the correct starter value (vs making count not private anymore):

Now the test looks like this!

That’s it! By adding an init function that injects the initial value we want, we keep the integrity of our Counter as it was intended while writing effective tests for it!

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

  • Stuart Kent

    Any reason to avoid using a default value to reduce back down to a single constructor? I find this pattern helpful to inject the “real” dependency by default in prod code (for convenience), but allow overwriting very easily when desired in test code 🙂

    init(count: Int = 0) {
    self.count = count

    • That works as well, but it’s not as explicit b/c of autocomplete. When the count is set with a default value, I am still prompted to initialize with an initial value via autocomplete.

    • Just tried it, and autocomplete seems to be the same with the default value! Looks like they fixed it! I’ll update to this 🙂 Thanks!

      • Stuart Kent

        I see you updated the code, but there’s a small typo in the tweaked version. It should read:

        • good catch! updated.

          • Vince O’Sullivan

            Strange. Two days later, I still see the original, incorrect code and not the correction.

          • Soren Mortensen

            Yeah, so do I

  • Hi,
    Why not using an extension instead of changing the original struct for test purposes?
    Something like:

    • Init has to be used, so I like to make it clear that it’s there. I only like to use extensions for private helper functions to separate them from the accessible interface code.

  • There is a typo in your code:
    init(count: Int = 0) {
    count = 0
    The parameter isn’t used.

  • Vince O’Sullivan

    The intricacies of unit testing are such that there is often never a perfect solution about how best to test. That said, I’m always worried by artefacts introduced into working code solely for testing purposes rather than for it’s actual use. I appreciate that this example is “toy code”, nevertheless I couldn’t help making the following observations:

    The original code contained the following three comments (which I’ll assume for the purposes of the argument are derived from a spec.)…

    1. // … I never want to set the count externally,
    2. // I want the counter to start at 0 by default
    3. // I only want the count to be modified via functions like these

    In order to simplify your testing you have broken comments 1 and 3 above and widened the comment 2 (which is a weak comment since it implies but doesn’t state that other values are permissible, but is in any case still covered by both other comments) to allow non-zero starts.
    The apparently innocuous change appears to have introduced a spec. breaking vulnerability into the code solely for the purposes of testing that could equally be used for malicious hacking purposes (perhaps by initialising the counter with a negative number).

  • Serge Sukhanov

    What did you use to embed swift source code to your blog? Please tell. I’ll be very thankful. 🙂