Unit Testing is an art form. As developers we probably all know the importance of writing unit tests, even if that's only in theory. Many of us would like to say we regularly do it, but the reality is that it's time consuming, takes practice, and can involve writing a ton of unnecessary code if we don't understand it properly.
So, what is a dependency? It's just the concept of one thing relying on some other thing, and in software engineering that's often a concrete relationship between objects, one intrinsically embedded into another. When you're first learning about this it can blow your mind a bit, you think "but this thing IS embedded into that!" - a file system is part of the computer, a web service is called by an app, and they are... but it's not about breaking the association between things, it's just about breaking the concrete dependency on what the things are.
How do we break dependencies? Largely, it's all about using common interfaces. Defining how an association should be made without defining exactly what makes it. Take the example of a USB device, it uses a common interface - the USB connector. Your phone isn't dependent on your wall socket to charge, it can plug into a portable battery, or your laptop, or your car. USB devices aren't dependent on exactly what's at the other end as long as it also supports the behaviours defined by the USB protocol, which in this example is the ability to charge from a power source. Remember when phones all had specific chargers, and you had to have the exact lead, with the exact connector, the exact power block, and even the exact power supply? ...those were concrete dependencies and USB is a perfect example of breaking dependencies by providing a common interface.
This is somewhat generalising a wider issue though, good software design goes a long way to breaking dependencies in the first place, and you don't always need to break every dependency to write effective unit tests either; sometimes things genuinely are and should be coupled together. Continuing with the USB charger example, would you want to break the dependency between the charger lead and the wall plug? Yes, almost certainly, there are many different types of plug for different power supplies in different parts of the world, and there are various different types of lead for different phone manufacturers. It's a valid use case to make them independent and interchangeable. Would you want to break the dependency between the plug and its pins? Or the charger lead and each individual wire within it? Probably not. If we're thinking about things from a testing perspective, the real question is whether these things need to be tested individually. In the case of a charger lead and its wires, or a wall plug and its pins, you're probably going to test these things as whole units, in which case you wouldn't break them into bits for no real gain. In the case of the wall plug and the lead, you would. The failure of one shouldn't impact the test results of the other, you shouldn't need the lead to tell you the plug is working, and vice versa. That just convolutes the test. So you break the dependency between them with an interface and treat them as separate entities.
Use, and over-use of Interfaces
Now, there's a discussion to be had (and plenty have been had) on the use of interfaces, or rather the over-use of them. Does every single dependency in a class need to broken and substituted with an interface? No. Are concrete dependencies a bad thing? Not always. I read a really good breakdown of this online the other day using the Coordinate class as an example. This class essentially has two members: point X and point Y. Would it add any benefit by extracting an interface here? It already describes its behaviour well enough. Are you going to have a hard time unit testing a class which contains it? No, it doesn't contain any further dependencies and it's essentially just adding a couple of properties which are probably characteristics of your class anyway. Would substituting it for something else make sense, or benefit testing? No, you'd just be duplicating it's functionality. It doesn't always make sense to extract interfaces, but in many cases it does. Ask yourself if you're depending on this implementation and its intrinsic functionality, or just the behaviour it represents? As with many things in software engineering there's various factors to consider. There are reasons to apply patterns, and there are reasons not to, and there are few hard and fast rules that apply across the board. It's down to us as developers to consider these things when making those decisions, especially when it comes to unit testing.
So what about Dependency Injection? That sits right at the core of breaking dependencies, and it generally works by using common interfaces. It's essentially the process of having them passed in at runtime, rather than being baked in. There's a variety of frameworks that do the actual injection, most work by passing the dependencies in via a constructor when an instance is created, but it's largely irrelevant which one you use, they all achieve the same goal. Strictly speaking you don't even need to have a DI framework, they just make the process easier. You don't necessarily have to have dependencies passed in via a constructor either, as long as there's some way to set them up dynamically. It's this ability that makes unit testing possible, because a unit test is itself a dependency injector, it just passes in mocked versions of the dependencies in order to isolate the code that's being tested. It should also be noted that dependency injection doesn't have to be done with actual interfaces; you could say a base class or an abstract class is a common interface. The point is, to write effective, succinct, isolated unit tests, you need to be able to inject dependencies.
Once you've figured all that out, you've identified code that needs to be tested, you've identified dependencies that need to be broken within that, you've broken them with common interfaces, and you're able to inject mock dependencies in their place.. you're then left with the daunting task of creating these mock dependencies. This can be, and to many people still is, one of the most difficult, or at least time-consuming aspects of Unit Testing.
In Part 2 I'll discuss three great tools that can help you rapidly create, populate and test these mocks, with as little boilerplate code as possible, written in a syntax that's both readable and easy to write.