Inverting Your Dependencies
Android app development winds up using a fair number of singletons, such as the repositories that we have used in several preceding chapters. So long as those singletons do not result in an unexpected memory leak, and so long as they are part of some organized architecture, singletons can be fine.
The problems kick in when you need different “singletons” in different situations. For example, you might need a singleton that responds in certain ways for testing, compared to the “real” singleton that you need for your production app use.
So, even though singletons are largely unavoidable in modern Android app development, how we declare and get those singletons can vary. So far, our repositories have been static
fields in Java or object
classes in Kotlin. Those are simple but inflexible. Dependency inversion allows us to still use singletons, yet be able to replace them as needed, such as for testing.
The Problem: Test Control
Let’s revisit the diceware samples from earlier in the book. In both DiceLight
and Diceware
, we have a PassphraseRepository
. In Kotlin, it is declared as an object
; in Java, it is declared as a class with a static field holding a singleton instance. And, both work, using a SecureRandom
as the source of random numbers for use in generating the passphrase.
But now, suppose we want to test that code.
Random numbers are messy when it comes to tests. Truly random numbers are not repeatable, and so that makes our tests difficult to write. The way you test data backed by random numbers is to “seed” the random number generator, so that while it generates random numbers, those random numbers are consistent across uses of the seed. Two random number generators started with the same seed will generate the same random numbers. Now, we have repeatable data, suitable for testing… but we do not want to use a fixed seed for the actual production use of the app. In the case of SecureRandom
, it will seed itself from “sources of entropy” by default, thereby providing more truly random numbers.
So, for testing, we want a manually-seeded SecureRandom
. For production, we want an automatically-seeded SecureRandom
. Yet our singleton repository has just the one SecureRandom
, and we do not have a great way in the PassphraseRepository
to know whether we are running as part of a test or not.
We could say that the PassphraseRepository
takes the SecureRandom
as a parameter. Tests can pass in the seeded SecureRandom
; production code could pass in a regular SecureRandom
. For this limited scenario, we could make that work. However, this becomes unwieldy if we have hundreds of things that vary based on testing and need to pass those objects through several layers of app code.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.