C#: dependency injection makes code testable
Pass a dependency in through the constructor so tests can replace it with a fixed one.
using System;
interface IClock {
int Hour { get; }
}
class SystemClock : IClock {
public int Hour { get { return DateTime.Now.Hour; } }
}
class FixedClock : IClock {
public FixedClock(int hour) {
Hour = hour;
}
public int Hour { get; }
}
class Greeter {
private readonly IClock clock;
public Greeter(IClock clock) {
this.clock = clock;
}
public string Greet() {
return clock.Hour < 12 ? "Good morning" : "Good afternoon";
}
}
class Program {
static void Main() {
Console.WriteLine(new Greeter(new FixedClock(9)).Greet());
Console.WriteLine(new Greeter(new FixedClock(15)).Greet());
}
}Good morning Good afternoon
Explanation
Dependency injection means a class receives the objects it depends on instead of creating them. If Greeter called DateTime.Now itself, its output would change with the time of day, and a test could not control it. Depending on an interface lets callers supply any implementation.
In production you would pass a clock that reads the real time. In a test you pass a fake clock fixed at the hour you need, so the result is deterministic. This is the dependency inversion principle: high-level code depends on an abstraction, not on a concrete class. Frameworks such as Microsoft.Extensions.DependencyInjection wire the objects together automatically.
Explore the dry run
using System; interface IClock { int Hour { get; }} class SystemClock : IClock { public int Hour { get { return DateTime.Now.Hour; } }} class FixedClock : IClock { public FixedClock(int hour) { Hour = hour; } public int Hour { get; }} class Greeter { private readonly IClock clock; public Greeter(IClock clock) { this.clock = clock; } public string Greet() { return clock.Hour < 12 ? "Good morning" : "Good afternoon"; }} class Program { static void Main() { Console.WriteLine(new Greeter(new FixedClock(9)).Greet()); Console.WriteLine(new Greeter(new FixedClock(15)).Greet()); }}Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| The abstraction that Greeter depends on. | — | — | |
| The dependency is supplied from outside, through the constructor. | — | — | |
| A fake clock fixed at 9 o'clock makes the result predictable. | Hour = 9 | Good morning | |
| The same class gives a different answer for 15 o'clock. | Hour = 15 | Good morning Good afternoon |
Cost and common mistakes
O(1) per call.
- Creating the dependency inside the class with new makes tests slow, random, or dependent on the environment.
- Do not create an interface for every class. Introduce one where behavior needs to vary or be replaced in tests.
Try it yourself
Where would SystemClock be used?
Reveal answer and reasoning
In the real program: new Greeter(new SystemClock()) reads the actual time, while tests use FixedClock.