dependency injection in .net applications
TRANSCRIPT
Dependency Injection with .NET
-OR-
So you’ve decided to finally inject your dependencies
-OR-Let’s test production scenarios so it
doesn’t break
Overview
• What is DI?
• Why use DI?
• Where to use DI?
• How to do DI?
• C#, Legacy Code, and DI
– Interfaces, and Singletons, and Mocks, oh my!
What is Dependency Injection
• A software design pattern that implements inversion of control for resolving dependencies. A dependency is an object that can be used (a service). An injection is the passing of a dependency to a dependent object (a client) that would use it.– Wikipedia
AppIoCDependency
ConcreteImplementation
ConcreteImplementation
ConcreteImplementation
Dependency Inject – An Example
• Legacy code w/o DI
public class ReviewManager{
public ElasticSearchBackedReviewRepository ReviewsRepo
{get; set;}
public void GetReviewsByMovie( int movieId){
return ElasticSearchBackedReviewRepository.GetReviews(movieId);
}
}
• Legacy code w/o DI
public class ReviewManager{
public IReviewRepository ReviewsRepo {get; set;}
Public ReviewManager( IReviewRepository repo ){
ReviewsRepo = repo;
}
public void GetReviewsByMovie( int movieId){
return ReviewsRepo.GetReviews(moviedId);
}
}
AppIoCDependency
ConcreteImplementation
ConcreteImplementation
ConcreteImplementation
Why Interfaces?
• Develop against a contract
– Business logic isn’t changed as new implementations are introduced
• Moq (for unit tests) requires interfaces or virtual methods.
• All wiring for dependencies can be performed in a centralized location
Why Mocking?
• Allows testing any use case without the need for test data
– Mock a response that returns the use case you are testing (as opposed to test data in the DB)
– Null return values
– Timeout (web service clients)
– 0 reviews, 1 review, 10000 reviews
When? Architecture Boundaries
App (Front End)App (Business Logic)
DB 1
DB 2
Service 1
Service 1
Service 1
Any time the app reaches out for data is a good candidate for dependency injection.
In this scenario, the clients we use to wrap the 2 databases and the 3 services would each implement an interface that could be mocked for unit tests
When? Responsibility Boundaries
• Fandango Desktop/Mobile Web
– Reviews (Direct DB calls, Elastic search service)
– Movies (DB calls, movie service)
– Theaters (DB calls, Commerce service, old service, new API)
DI via Delegates
• Initial Effort is low– No extra frameworks– unit test can define the mocked method
• Subsequent efforts are O(N)– One change per new mocked method
• Pros– Methods can be incorporated one at a time
• Cons– Each new method will require its own delegate and
new wiring.
DI via Mock POCOs
• Initial Effort is medium– New class for each mock– Logic for mock states
• Subsequent efforts– Potential for new forks (and bugs) in mock POCO logic for
each use case
• Pros– Don’t need to learn a new framework.
• Cons– Supporting all use cases increases potential for bugs in the
mocks.– Some dependencies can’t be mocked
1
1 – With Moq, you can only mock methods defined in an interface or virtual methods.
DI with Mocks (Moq)
• Initial Effort is high– Each unit test use case requires its own moq wrapper
per dependency
• Subsequent changes– Only changes to the contract require modifications to
your mocks
• Pros– Each tests clearly identifies its assumptions
• Cons– Lots of unit test code will just be setting up the
dependency
Legacy Code: Static Methods
• Interfaces can’t have static methods
• Convert static methods to instance methods that conform to the (new) interface with a Singleton to expose the static instance.
ScenarioProduction issue with unknown cause
• Scenario in production we can’t reproduce easily
• In a unit test, mock the underlying interface to the throw the same exception.
– At the very least, we’ll know how to stop this error from crashing the entire app and set up proper logging to identify potential causes
ScenarioProduction issue with unknown cause
[Fact]
public void TestIndex_WebExceptionFromReviewManager()
{
InjectCookieCollectionDependency();
var mock = new Mock<IReviewManager>();
mock.Setup(rm => rm.GetEsReviewsByMovie(
It.IsAny<int>(),
It.IsAny<int>(),
It.IsAny<int>(),
It.IsAny<UserReviewSort>()))
.Throws(new WebException());
IReviewManager reviewManager = mock.Object;
using (var controller = new MovieController(reviewManager) { ControllerContext = new ControllerContext { HttpContext = new MockHttpContext() } })
{
MockHttpContext httpContext = configureContext(UserAgentValue);
controller.ControllerContext.HttpContext = httpContext;
ActionResult result = controller.Index(MovieIdAmericanBeauty);
Assert.IsType(typeof (ViewResult), result);
}
}
ScenarioAnonymous + authenticated users
• Our apps identify a logged in user by the presence of a cookie. We can mock that!– See changes to MovieController with
ICustomerCookieProvider
Strategies for Unit Testing
• New Projects
– TDD allows us to work with QA to identify good test cases before we start writing code
• Legacy Projects
– Don’t test something that’s been working in production for years
– Create unit tests to reproduce your bug. This will allow for immediate ROI on the tests
– Introduce tests for new features
Side Effect (Good)
• Enforces the SOLID principlesSingle Responsibility [link]
If an interface crosses domains, you know to split themMultiple interfaces too many responsibilities for the class
Open/Closed Principle [link]
Your code is inherently open to extension
Liskov Substitution Principle [link]
Your mocks provide are substituted for the concrete implementations without code changes
Interface segregation Principle [link]
Interface contract hides helper methods
Dependency inversion principle [link]
This is what we’re talking about here
Further Reading
• Martin Fowler (http://martinfowler.com/articles/injection.html)
• AutoFac IoC container (http://autofac.org/)
• AutoFac in action NuGet Gallery – DI by Environment