What is Dependency Injection?
Dependency injection is a design pattern that promotes loose coupling between classes by allowing dependencies to be provided from external sources. Instead of a class creating its own dependencies, those dependencies are passed to the class through its constructor, properties, or method parameters. This allows for easier testing, as dependencies can be easily mocked or stubbed during unit testing.
How to Implement Dependency Injection in C
To implement dependency injection in C#, we need to follow a few steps:
-
Identify the dependencies of the class: The first step is to identify the dependencies that the class requires to function properly. These dependencies can be other classes, interfaces, or services.
-
Create interfaces for the dependencies: Once the dependencies are identified, it is recommended to create interfaces for them. This allows for easier mocking and stubbing during unit testing.
-
Modify the class to accept dependencies through its constructor: The next step is to modify the class to accept its dependencies through its constructor. This is known as constructor injection. By doing this, the class becomes more flexible and easier to test.
-
Use a dependency injection container: To manage the creation and resolution of dependencies, we can use a dependency injection container. A container is responsible for creating instances of classes and resolving their dependencies.
Example: Unit Testing a Class with Dependency Injection
Let’s consider an example where we have a MainClass
that depends on a logger and a database. Here’s how we can refactor the MainClass
to follow the explicit dependencies principle:
public class MainClass : IMainClass
{
private readonly ILogger logger;
private readonly IDatabase db;
public MainClass(ILogger logger, IDatabase db)
{
this.logger = logger;
this.db = db;
}
public void AddDetails(Data data)
{
// do some business operations
db.Add(data);
logger.Information("added");
}
}
In the above code, we have modified the MainClass
to accept its dependencies (ILogger
and IDatabase
) through its constructor.
To test the MainClass
, we need to mock the necessary dependencies. Here’s an example of how we can write a unit test for the AddDetails
method:
[TestClass]
public class MainClassTests
{
[TestMethod]
public void Should_AddDetails_To_Database()
{
// Arrange
var mockDb = new Mock<IDatabase>();
var data = new Data();
var mainClass = new MainClass(Mock.Of<ILogger>(), mockDb.Object);
// Act
mainClass.AddDetails(data);
// Assert
mockDb.Verify(_ => _.Add(data), Times.Once);
}
}
In the above test, we create a mock object for the IDatabase
dependency and pass it to the MainClass
constructor along with a mocked ILogger
object. We then call the AddDetails
method and assert that the Add
method of the IDatabase
dependency is called once with the correct data.
Conclusion
In this article, we have explored how to unit test code that uses dependency injection in C#. By following the steps outlined above, we can effectively manage dependencies and write testable code. The use of dependency injection allows for easier mocking and stubbing of dependencies during unit testing, resulting in more reliable and maintainable code.