Constructor Injection
A class asks for its dependencies in its constructor and keeps them in readonly fields. Whoever creates it passes them in, so a test can pass a fake.
Journeyman · C# · OOP · Updated
After this lesson you can
- Inject a dependency through a constructor and keep it in a readonly field
- Pass a fake dependency in a test, so the result doesn't depend on the outside world
Key takeaways
- With constructor injection, a class asks for its dependencies as constructor parameters instead of creating them with
new. - Keep an injected dependency in a
readonlyfield, so it never changes once the constructor sets it. - In a test, pass a fake such as
new FixedClock(9), so the result is the same at any time of day. ProductDaoasking for its connection string is dependency injection too: the app or the test decides which database it uses.- ASP.NET Core has a built-in container that creates controllers and passes in what their constructors ask for, such as
IConfiguration.
Dependency injection means a class receives its dependencies instead of creating them. The most common way is through the constructor: the class asks for what it needs, as parameters, and keeps them.
Asking for the clock
Here's the Greeter from the last lesson, rewritten to ask for its clock:
Greeter greeter = new Greeter(new Clock());
Console.WriteLine(greeter.Greet("Ada"));
public class Clock
{
public virtual int Hour => DateTime.Now.Hour;
}
public class Greeter
{
private readonly Clock clock;
public Greeter(Clock clock)
{
this.clock = clock;
}
public string Greet(string name)
{
return clock.Hour < 12 ? quot;Good morning, {name}!" : quot;Good afternoon, {name}!";
}
}
Three small changes:
- The field has no
new.Greeterno longer decides which clock it gets. - The constructor asks for a
Clockand keeps it in the field. - The field is
readonly: once the constructor sets it, it never changes.
The code that creates the greeter, here new Greeter(new Clock()), now makes the decision.
A fake clock for testing
Because Hour is virtual, a test can pass a fake clock that always says the same time. A fake is a stand-in that behaves predictably:
Greeter morning = new Greeter(new FixedClock(9));
Greeter afternoon = new Greeter(new FixedClock(15));
Console.WriteLine(morning.Greet("Ada"));
Console.WriteLine(afternoon.Greet("Ada"));
public class Clock
{
public virtual int Hour => DateTime.Now.Hour;
}
// A fake clock for tests: it's always the hour it was given.
public class FixedClock : Clock
{
private readonly int hour;
public FixedClock(int hour)
{
this.hour = hour;
}
public override int Hour => hour;
}
public class Greeter
{
private readonly Clock clock;
public Greeter(Clock clock)
{
this.clock = clock;
}
public string Greet(string name)
{
return clock.Hour < 12 ? quot;Good morning, {name}!" : quot;Good afternoon, {name}!";
}
}
Good morning, Ada!
Good afternoon, Ada!
Now both greetings can be tested at any time of day, and every run gives the same answer. That's polymorphism at work: Greeter holds a Clock, and the object behind it can be the real clock or a fixed one. (In Abstraction and Interfaces, you'll meet a cleaner way to define what a clock must do.)
A test looks like this:
[TestMethod]
public void Greet_BeforeNoon_SaysGoodMorning()
{
Greeter greeter = new Greeter(new FixedClock(9));
string greeting = greeter.Greet("Ada");
Assert.AreEqual("Good morning, Ada!", greeting);
}
You've already used it
The connection string has been injected all along. ProductDao asks for it in its constructor:
public ProductDao(string connectionString)
{
this.connectionString = connectionString;
}
So the app decides which database the DAO uses, and the exercise's tests decided for themselves: they passed their own connection string, which could point at any database, without a change to the DAO. And in ASP.NET Core MVC, ProductsController asked for IConfiguration in its constructor, and ASP.NET Core passed it in. That's dependency injection too: ASP.NET Core has a built-in container that creates controllers and hands them what they ask for. ASP.NET Core MVC in Depth shows how to register your own classes with it.
Check yourself
2 questions to try on the lesson page.
So what
Ask, don't make: a class that asks for its dependencies in its constructor can be given the real ones in the app and fakes in the tests. That one habit makes code testable, and it's how every ASP.NET Core app is built.