One of the biggest struggles that developer have with adopting Unit Testing, whether it’s Test Driven Development (TDD), Behavior Driven Development (BDD) or even just Test Eventual Development (TED), is the difficulty some code is to test. This is typically are problem when code doesn’t follow the SOLID design principles. If you aren’t familiar with Robert C. Martin’s SOLID Principals, take a moment to read his paper. Go ahead, I’ll wait.
Finished it? Great. Let’s talk about how SOLID can help make the day in the life of a .NET developer better.
I was working on a large ecommerce project not too long ago, and a developer wrote the following code:
public bool LoginUser(string userName, string password)
{
LoginService service = new LoginService();
return service.ValidateUser(userName, password);
}
There are several things wrong with this (as the developer figured out). Let’s dissect the issues:
Are we there yet?
If you are responsible for this section of code, and another team member (or third party organization) is responsible for delivering the service, you can’t compile your code until the service is done. This adds a lock to the development process, ensuring that the code moves forward on a single thread, crippling productivity.
It’s not my code that broke!
How many times have you heard that? The login screen doesn’t work. Where is the offending code? Is it the LoginService, or that class that holds the method LoginUser? Now we have to step through the code to see where it breaks, and we all know how much fun that is…
The code is doing too much aka the “All of my eggs are in one basket” principle
How is that? There’s only two lines! It’s not the line count that matters, it’s the content. In those two lines, the Unit of Work (in C#/VB.NET this is a method) is instantiating an object AND using it.
Because this method is responsible for creating and using the LoginService, it is tightly coupled to a single implementation. What is the organization want to centralize the user validation service? Instead of using this particular implementation contained in the LoginService class, the code needs to call the CentralizedLoginService class, looking like the following code block.
public bool LoginUser(string userName, string password)
{
CentralizedLoginService service = new CentralizedLoginService();
return service.ValidateUser(userName, password);
}
What changed? Just the type of service that was instantiated. Nothing else. Yet this means a recompile and a redeploy, and that is usually not a trivial exercise. Lots of wasted cycles that could have been used to further the feature set of the application.
This breaks the Single Responsibility Principle – “A class should have one, and only one, reason to change.”
Let’s get abstract
We already saw the problems with creating and using an object in the same unit of work. The first step in fixing this is to abstract the implementation away from the invocation. We do that by making sure we are always coding to an interface.
The Dependency Inversion Principle states “Depend on abstractions, not concretions”.
We have to extract the interface from the classes, and update our code to declare the instance as an interface instead of a type. (Yes, I could use “var”, but that gets away from the points we are making).
public bool LoginUser(string userName, string password)
{
ILoginService service = new CentralizedLoginService();
return service.ValidateUser(userName, password);
}
But we can’t instantiate an interface, so we have to use another way to create the instance. One way to resolve this is to use a factory pattern for object creation. When we create the factory object that returns an instance of an interface, we get code that might look like this:
public bool LoginUser(string userName, string password)
{
ILoginService service =
LoginServiceFactory.CreateService(ServiceType.Centralized);
return service.ValidateUser(userName, password);
}
Better. The only thing that needs to change is the factory when the implementation needs to change. And since we are depending on the abstraction of the login service, it doesn’t matter to the line of business code which implementation is passed back from the factory.
This also gets us in line with another SOLID principle.
The Liskov Substitution Principle states “Derived classes must be substitutable for their base classes.”
We can make any number of subclasses (as long as they implement the ILoginService interface), and the line of business code will chug along happily ever after.
Really? A whole class just to satisfy a principle?
We had to make a whole new class just to separate out the instantiation from the use. That’s extra code, again taking away from moving the project forward. There is a better way to resolve this issue, and it involves a lot less code. The pattern is called Dependency Injection, or DI for short. As the name implies, all of the dependencies for a class or method are injected in, creating a better separation of concerns without having to create a lot more code.
There are several types of DI, but what we are going to talk about here is Constructor Injection. Again, as the name implies, all of the dependencies are injected into the constructor of the class. The ensures that all of the necessary scaffolding for the class are there when the class is instantiated, eliminating the need to a lot of guard clauses. (There are scenarios where setter injection and the other types provide value, but that is for another post).
We again refactor the code to use constructor injection, and end up like this:
public class SecurityHandler
{
private readonly ILoginService _service;
public SecurityHandler(ILoginService service)
{
_service = service;
}
public bool LoginUser(string userName, string password)
{
return _service.ValidateUser(userName, password);
}
}
Summary
What have we accomplished? We changed our code so that it only does the one thing that is it supposed to, we removed the concrete implementation, and we injected our dependencies. The code is still clean and concise, and pretty much the same number of lines that we had before the refactoring.
We are no longer dependent on the LoginService being implemented before we can test our code, get it checked in, and move on to the next feature request. We can test this code to make sure that it behaves as we expect, knowing that the dependencies won’t occlude the results.
In my next post we will pick up from here to show how this new version of the code provides these benefits.
Happy Coding!