C# · Course navigation

C#: dependency injection makes code testable

Pass a dependency in through the constructor so tests can replace it with a fixed one.

On this page
Before you start

C# interfaces and classes.

  • Pass a dependency in through the constructor so tests can replace it with a fixed one.
  • Predict the sample output and explain the state changes.
  • Complete the practice task and check the boundary cases.

The concept

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.

Code example

C#
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());
  }
}
Output
Good morning
Good afternoon

Follow the code

Open the walkthrough to inspect the example one step at a time.

Show guided walkthrough
Guided walkthroughReady
1using System;
2
3interface IClock {
4 int Hour { get; }
5}
6
7class SystemClock : IClock {
8 public int Hour { get { return DateTime.Now.Hour; } }
9}
10
11class FixedClock : IClock {
12 public FixedClock(int hour) {
13 Hour = hour;
14 }
15
16 public int Hour { get; }
17}
18
19class Greeter {
20 private readonly IClock clock;
21
22 public Greeter(IClock clock) {
23 this.clock = clock;
24 }
25
26 public string Greet() {
27 return clock.Hour < 12 ? "Good morning" : "Good afternoon";
28 }
29}
30
31class Program {
32 static void Main() {
33 Console.WriteLine(new Greeter(new FixedClock(9)).Greet());
34 Console.WriteLine(new Greeter(new FixedClock(15)).Greet());
35 }
36}

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
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput 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

Behind the scenes

How C# processes this code

C# compiles to code for the .NET runtime, which invokes the program entry point.

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.

Further reading