Skip to main content
  1. Posts/

Dependency Injection, Architecture, and Testing

Chris Ayers
Author
Chris Ayers
I am a father, nerd, gamer, and speaker.

This blog was posted as part of the Third Annual C# Advent. Make sure to check out everyone else’s work when you’re done here

Dependency Injection, or DI, is a Software Architecture Design Pattern. DI is something that comes up during discussions on SOLID, IoC (Inversion of Control), testing, and refactoring. I want to speak on each of these briefly because DI touches all of these. But before I really dive into things, I want to define what a dependency is. A dependency is any object that another object requires. So all of those classes, services, and libraries that we use to build our applications are dependencies.

Dependency Injection - What is it?
#

At its heart, Dependency Injection is right there in the name, you inject your dependencies. I want all the dependencies for my class called out in the constructor, parameter list, or somewhere I can easily see them. Without Dependency Injection, any time I need a class, service, database connection, I just new it up. This can make code untestable, hard to use safely, and very brittle. Lets see what this looks like in a small example. Imagine the hidden dependencies and hard to test logic in a bigger class.

    public class ClientService
    {
        private ClientRepository _clientRepository;

        public ClientService()
        {
            //allocated in constructor but can't test or use a different repository without a code change
            _clientRepository = new ClientRepository();
        }

        public void Delete(int id)
        {
            //hidden dependency to the delete method, newed up here
            var audit = new AuditService();
            audit.LogDelete(id);

            _clientRepository.Delete(id);

            //a hidden static dependency, very hard to test with
            EmailProvider.SendDeleteConfirmation(id);
        }
    }

Untestable
#

I said not using DI would make the code untestable. Why? There are a lot of reasons in this example. I’ll go through them one at a time.

Isolation / Coupling
#

When you are writing unit tests, you want to isolate a class for testing. Some people call it the “System Under Test” (SUT) or “Class Under Test” (CUT). How can we isolate just the logic in ClientService from the logic in its dependencies? Our service is tightly coupled to the repository, the audit service and the email provider. I can’t easily put in a fake/mocked email provider or audit service to test what my class is doing. This type of code pushes a lot of developers to skip unit tests and just rely on integration tests.

Database
#

Another issue is I/O, like files, databases, network connections. In this example, I’m using the Repository pattern to wrap/abstract my database. This could be a file, mysql, or Azure CosmosDB, I don’t care. But, in this case, it’s getting created in the constructor of my service. When I want to unit test my business logic, it will be touching the database. More of an integration test than a unit test. Unit tests should be fast. Ludicrous speed fast. Sure I could create a local or testing database, but again, that is integration, not unit testing.

Statics
#

Statics are always a problem because statics can’t be mocked or faked. Another issue is that its hard to tell side effects of those classes and methods. When I’m reviewing a class or method, I usually check the constructor, parameters, or variables at the top to see what a piece of code depends on. Static methods can be buried inside a block of code and be missed. It means that it can be a hidden dependency.

Interfaces
#

I feel it is very important to have interfaces define the contracts between layers and systems. Having FOO depend on BAR means that FOO might change every time we modify the implementation of BAR. If FOO depends on IBAR, the API contract of BAR, we can have multiple implementations, change the implementation or mock it. The interface insulates FOO from the changes in BAR. In addition to reducing coupling between modules, it will help with testability and refactoring down the road.

Types of Dependency Injection
#

Let’s look at a few types of dependency injection. The type I usually prefer, and my default, is Constructor Injection. This lists all the dependencies for a class in its constructor. There are times where this isn’t a good idea, when you have the expensive creation of dependencies that are only used in infrequent edge cases in the class. In those instances, Parameter Injection works well, injecting the dependency as a parameter into the methods that need it. Finally there is property injection which I dislike. I’ve seen people use it because of tutorials online, but I HIGHLY recommend avoiding this if possible.

Constructor Injection
#

This involves moving dependencies to the constructor where possible. This is a lot of times the place where people end up, but if you’re refactoring older code, you won’t get here right away. Notice I moved from having private variables of the concrete classes to the interfaces. Also notice, looking at the class constructor, you know exactly what this class depends on.

    public class ClientService : IClientService
    {
        private readonly IClientRepository _clientRepository;
        private readonly IAuditService _auditService;
        private readonly IEmailProvider _emailProvider;

        public ClientService(IClientRepository clientRepository, IAuditService auditService, IEmailProvider emailProvider)
        {
            _clientRepository = clientRepository;
            _auditService = auditService;
            _emailProvider = emailProvider;
        }

        public void Delete(int id)
        {
            _auditService.LogDelete(id);
            _clientRepository.Delete(id);
            _emailProvider.SendDeleteConfirmation(id);
        }
    }

Parameter Injection
#

This involves moving dependencies to where they are used. Looking at the interface to the service or the methods, you can see what dependencies are needed for some of the methods. Like I mentioned before, if there are some dependencies that are really expensive to create or maybe incur some type of cost, you can limit their creation to only when they are used.

    public class ClientService : IClientService
    {
        private readonly IClientRepository _clientRepository;

        public ClientService(IClientRepository clientRepository)
        {
            _clientRepository = clientRepository;
        }

        public void Delete(int id, IAuditService auditService, IEmailProvider emailProvider)
        {
            auditService.LogDelete(id);
            _clientRepository.Delete(id);
            emailProvider.SendDeleteConfirmation(id);
        }
    }

Property Injection
#

Use property injection only for optional dependencies. That means your service should be able to properly work without these dependencies provided. After you instantiate the class, you need to assign the properties with objects. This can cause confusion because you can’t always tell what a classes true dependencies are just looking at the constructor. It can also cause confusion during testing.

    public class ClientService : IClientService
    {
        public IEmailProvider EmailProvider { get; set; }

        private readonly IClientRepository _clientRepository;
        private readonly IAuditService _auditService;

        public ClientService(IClientRepository clientRepository, IAuditService auditService)
        {
            _clientRepository = clientRepository;
            _auditService = auditService;
        }

        public void Delete(int id)
        {
            _auditService.LogDelete(id);
            _clientRepository.Delete(id);
            EmailProvider?.SendDeleteConfirmation(id);
        }
    }

DI Containers
#

Having all our dependencies in the constructor is nice, but who is going to new up our services? With most DI frameworks, you register your interfaces and services at program startup with a DI container, and everything is wired up for you. You can control the wiring process. Should service A be a singleton? Should service B be instantiated on every call? Dotnet core has built-in support for DI containers. And before dotnet core, the .Net Framework had a lot of different libraries and frameworks to help with dependency injection. You might have heard of Ninject, Autofac, Castle, or Unity. Dotnet core has some good documentation around their implementation, but if you want to you something else you can.

    services.AddScoped<IMyDependency, MyDependency>();
    services.AddTransient<IOperationTransient, Operation>();
    services.AddScoped<IOperationScoped, Operation>();
    services.AddSingleton<IOperationSingleton, Operation>();

Testing
#

I just briefly wanted to discuss testing. Using some of the techniques I’ve discussed, testing services and classes is much easier. By having interfaces for all my major services, constructor injection in place, and a mocking framework, I can isolate classes for testing.

        private BookService _bookService;
        private Mock<IBookRepository> _mockBookRepository;
        private Mock<IBookEntityDomainAdapter> _mockBookEntityDomainAdapter;

        [TestInitialize]
        public void TestInitialize()
        {
            _mockBookRepository = new Mock<IBookRepository>();
            _mockBookEntityDomainAdapter = new Mock<IBookEntityDomainAdapter>();

            _bookService = new BookService(_mockBookRepository.Object, _mockBookEntityDomainAdapter.Object);
        }

        [TestMethod]
        public void GetAll_ItemsInRepository_ReturnsSameNumberOfItems()
        {
            //arrange
            var sampleData = Enumerable
                .Repeat(new BookEntity(), 5)
                .ToList();
            _mockBookRepository.Setup(x => x.GetAll())
                .Returns(sampleData);

            //act
            var result = _bookService.GetAll();

            //assert
            Assert.AreEqual(sampleData.Count(), result.Count());
        }

Where to Go from here - Refactoring
#

Taking legacy code, code without tests (essentially the same thing), or code written without dependency injection and refactoring it into testable, decoupled code is not as easy as installing a nuget package. If you are just getting started and you have a ton of new ServiceX() and ServiceY.action() sprinkled throughout your code, code review the docs at: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.0. Have interfaces for your classes. When you have dependencies, instantiate them in your constructor if you can’t setup DI, it will help with readability and maintainability.

Conclusion
#

Dependency Injection is a pattern that everyone should be using. It makes the code more readable, maintainable, and testable. I know a lot of people think that this is something we’ve solved and everyone uses, but we’re not. Too many people aren’t setting up DI, they aren’t using interfaces, they aren’t registering dependencies. We need to keep discussing this because people are constantly starting out, learning, and growing in this field.

There is some sample code at: https://github.com/Codebytes/DependencyInjectionDemo

Related

RESTful API Versioning

·505 words·3 mins
I’ve been a developer for a long time, writing APIs and clients to consume them. When an API is around long enough, it needs to change. I’ve versioned APIs in the past using a number of different techniques. Some successful, some painful. Now I realize this discussion is like the VI/Emacs conflict, the Tab/Space wars, and the Spanish Inquisition, but it is a good topic to look at. There are a few main styles when it comes to API versioning:

Presenting with VS Code - Screencast mode

·243 words·2 mins
I have been starting to speak and present a lot more, and was looking into great tools like Carnac and KeyPosé. But I just found out today about a feature I didn’t know existed inside Visual Studio Code, Screencast mode. This was introduced in January 2019. How did I miss it? To enable and use Screencast mode, Open the command palette, Ctrl + Shift + P. When first enabled, Screencast Mode is not what I wanted. It shows EVERY keypress. It also shows a little higher on the screen that I prefer. It also puts a little red circle everywhere I click the mouse, which is nice. Let’s configure it and see if we can clean up some of that. Open the command palette again (Ctrl + Shift + P) and go to the user settings.

ARM - Part 3: Hook up the Pipes

·211 words·1 min
I’ve got a template straight from Microsoft. I want this wired into a CI/CD pipeline to I can play around and get quick feedback. I’m going to use Azure DevOps to help make all this possible. Let’s get those templates into a repository to get started. New repository, initialize it, add new files. Next, I’m going to create a new resource group to play around with my web app resources.

ARM - Part 2: Azure Quickstart Templates

·528 words·3 mins
Time to Dive in # I’m one of those guys that likes to learn by doing. Reading the documentation is great, and I do that a lot. But for me to really grok something, I need to play with it, run it, and probably blow it up. If you missed part 1, read along and come back. I need a WebApp setup for my sample project. I realized I can do it a few ways. Some of the ways are very manual, some are repeatable, but one stood out to me.

ARM - Part 1: Azure Resource Manager

The Journey Begins # I’ve been an azure developer for years. Originally I worked with “Classic Mode” and Cloud Services. Then I moved to ARM and Web Apps. Lately I’ve been doing DevOps but I only recently started working with ARM Termplates. First, let’s dive into a little history. History # Azure has grown and changed since it was first introduced. Originally, it was a research project called, “Project Red Dog”. Azure has been commercially available since 2010. For four years, there was a limited way to interact with Azure, ASM the Azure Service Manager.