So I took my first stab at adding unit tests to EA. One of the first hang ups I had to address was the fact that all the tutorials I'd seen dealt with code first projects, whereas EA was done using database first. Once you dive into it, the differences are pretty small, but it did necessitate deviating from the tutorial sample code. Basically, OdeToFood had the generic repository I wanted to use, but NerdDinner had the decoupling I needed to be able to work with the DbContext without actually touching it. So I finagled some things and came up with what I thought was an elegant marriage of the two. Added a constructor to the controller to allow for injection, refactored some code to use the generic repo, and voila! It's all hearts and flowers, mission accomplished! Or so I thought... until the lead programmer saw it. It didn't behave enough like the native DbSet syntax she'd been using, so it was back to the drawing board, and that's when I REALLY ventured down the unit testing rabbit hole.
Why Unit test?
I probably got the best run down of what makes unit testing worthwhile when I watched several videos explaining Microsoft Fakes (I'll discuss a little more in a bit but they are here, here and here). This article, Top 12 Reasons to Write Unit Tests, does an excellent job of enumerating a number of benefits. The long and short of it is that using tests to guide the design process leads to better code. In new development, programming with tests leads to code that is less prone to bugs, more easily maintained, and more easily refactored. Unit tests pin the behavior of the code under test, so changes to that code (or other code) that break functionality are caught immediately. Highly testable code tends to follow SOLID design principles, making code more maintainable and extensible.
Interfaces and Generic Repository
It took me a while to wrap my head around the idea of using interfaces, but fortunately the Ode to Food tutorial utilized an interface to enable unit testing, and this gave me a good baseline of how to decouple my controller from my DbContext. Going into EA, all of the controllers started out basically the same way:
public class ApplicationController : Controller { private EducationAssistanceEntities db = new EducationAssistanceEntities(); private IBranchUnitRepository _branchRepository; private IApplicationTermRepository _termRepository;
EducationalAssistanceEntities is the DbContext class, and the db variable is hit throughout the controllers, so all of the controllers were directly tied to the database. This controller also had branch and term repos attached to it. While the use of the interfaces was promising on the surface, there was no way to inject the classes implementing these interfaces. Instead, new objects are created within the methods that use these repos, and these new objects were also tied directly back to the DbContext:
public JsonResult GetUnitsByBranchID(int Branch_Id) { _branchRepository = new BranchUnitRepository(); var units = _branchRepository.GetAllUnitsByBranchID(Branch_Id);
public class BranchUnitRepository : IBranchUnitRepository { private LuBranch _dataContext; private EducationAssistanceEntities _ctx = new EducationAssistanceEntities();
On top of that, buried in there is yet another class called EAApplicationModel that ALSO is tied to the database context. (The User and Resquest objects will also prove problematic to testing but I'll address them later...)
public ActionResult Details(int? id) { if (id == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } EAApplicationModel model = new EAApplicationModel(id); model.user = User;
public class EAApplicationModel { private EducationAssistanceEntities _ctx = new EducationAssistanceEntities(); public Application Application; ... public EAApplicationModel(int? id) { //Go out and get their application Application = _ctx.Applications.Find(id); }
So a little bit of refactoring was inevitable. I had to get all these classes set up so that I could inject these dependencies rather than having everything so tightly coupled together. The basic pattern to do this was to inject the data context into the controller, which would then further inject it into these other classes. The OdeToFood demo does this by adding another constructor to the controller:
public class RestaurantController : Controller { IOdeToFoodDb _db; public RestaurantController() { _db = new OdeToFoodDb(); } public RestaurantController(IOdeToFoodDb db) { _db = db; }
One drawback of the OdeToFood example is that it also introduced me to the generic repo class that tripped me up with the project lead. Looked like this (term and branch repos came later...):
public class ApplicationController : Controller { //private EducationAssistanceEntities db = new EducationAssistanceEntities(); IEARepository _db; IPrincipal _user; public ApplicationController() { _db = new SQLEARepository(); _user = System.Threading.Thread.CurrentPrincipal; } public ApplicationController(IEARepository db, IPrincipal user) { _db = db; _user = user; }
public interface IEARepository { IQueryable<T> Query<T>() where T : class; void Add<T>(T entity) where T : class; void Update<T>(T entity) where T : class; void Remove<T>(T entity) where T : class; void SaveChanges(); void Dispose(); }
public class SQLEARepository : IEARepository { EducationAssistanceEntities db; public SQLEARepository() { db = new EducationAssistanceEntities(); //db.Configuration.ProxyCreationEnabled = false; } IQueryable<T> IEARepository.Query<T>() { return db.Set<T>(); } void IEARepository.Add<T>(T entity) { db.Set<T>().Add(entity); } void IEARepository.Update<T>(T entity) { db.Entry(entity).State = EntityState.Modified; } void IEARepository.Remove<T>(T entity) { db.Set<T>().Remove(entity); } void IEARepository.SaveChanges() { db.SaveChanges(); } void IEARepository.Dispose() { db.Dispose(); } }
There are no strongly typed collections of entities accessible on the _db object, instead you have to use the Query<T>() method. And there is no "Find" method at all any more. So this:
Application application = _db.Applications.Find(id);
Became this:
Application application = _db.Query<Application>().Where(a => a.Application_Id == id).SingleOrDefault();
Ultimately it wasn't the use of a generic repository that was the problem, but instead it was HOW I was using it. Reading through this article, "The generic repository is just a lazy anti-pattern", made me think about how I was using my repository. I think in the end I still ended up doing what he says not to do, but it got me thinking anyway. After a bit more research it occurred to me that the Unit of Work pattern would behave very much like the existing DbContext, minimizing the amount of refactoring I would have to do in order to make it work. Done right, I could basically drop the IUnitOfWork in for the EducationalAssistanceEntities class and most everything would work as is!
Repository and Unit of Work Patterns
Once it clicked that I needed a unit of work class with repositories as collections, I just had to figure out how to actually implement that. The first walk through I found, Entity Framework POCO (EF4): Generic Repository and Unit of Work Prototype, was helpful in that I started to understand the interaction of the unit of work with the repo, but it didn't implement "update", so I got frustrated and looked elsewhere. I found another walk through, Implementing the Repository and Unit of Work Patterns in an ASP.NET MVC Application, which proved to be a much more useful starting off point. It demonstrated how to use an interface with a repository, and how to build a generic repository and unit of work (though it didn't get into how to build a generic repo and unit of work with an interface, but the pieces were all there). Here are the interfaces I came up with:public interface IGenericRepository<T> : IEnumerable<T>, IQueryable<T>, IEnumerable, IQueryable where T : class { T GetById(Object id); IQueryable<T> GetAll(); void Insert(T entity); void Update(T entity); void Delete(T entity); void Delete(Object id); } public interface IUnitOfWork { IGenericRepository<Application> Applications { get; } IGenericRepository<AspNetRole> AspNetRoles { get; } IGenericRepository<AspNetUserClaim> AspNetUserClaims { get; } IGenericRepository<AspNetUserLogin> AspNetUserLogins { get; } IGenericRepository<AspNetUser> AspNetUsers { get; } IGenericRepository<C__MigrationHistory> C__MigrationHistory { get; } IGenericRepository<LuBranch> LuBranches { get; } IGenericRepository<LuDegree> LuDegrees { get; } IGenericRepository<LuEligibility> LuEligibilities { get; } IGenericRepository<LuGrade> LuGrades { get; } IGenericRepository<LuSchool> LuSchools { get; } IGenericRepository<LuState> LuStates { get; } IGenericRepository<LuUnit> LuUnits { get; } IGenericRepository<Term> Terms { get; } IGenericRepository<UnitPersonnel> UnitPersonnels { get; } IBranchUnitRepository _branchRepository { get; } IApplicationTermRepository _termRepository { get; } void Commit(); }
I ended up including the branch and term repos here to simplify injection. I wanted the repositories to be enumerable and queryable so that all the existing chained LINQ queries would work the same, so the generic repo interface inherits the IEnumerable and IQueryable interfaces. Now all of my most common functionality is contained in a single interface, and if I ever needed to make a more robust repository for a given object type, it would be simple to extend the interface and implementing class through inheritance (SQLGenericRepository implements IGenericRepository):
public interface IApplicationRepository : IGenericRepository<Application> { int SomeApplicationMethod(); } public class SQLApplicationRepository : SQLGenericRepository<Application> { public int SomeApplicationMethod() { return 42; } } public interface IUnitOfWork { IApplicationRepository Applications { get; } ... }
Production Unit of Work: Sql Implementation
Once I figured out the basic pattern the unit of work needed to follow, implementing the live version connected to the DbContext was pretty straight forward. Because it implements IEnumerable and IQueryable, the GetAll() method is probably superfluous.
public class SQLGenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class { internal EducationAssistanceEntities context; internal DbSet<TEntity> dbSet; public SQLGenericRepository(EducationAssistanceEntities context) { this.context = context; this.dbSet = context.Set<TEntity>(); } public TEntity GetById(object id) { return dbSet.Find(id); } public IQueryable<TEntity> GetAll() { return dbSet; } public void Insert(TEntity entity) { dbSet.Add(entity); } public void Update(TEntity entity) { dbSet.Attach(entity); context.Entry(entity).State = EntityState.Modified; } public void Delete(TEntity entity) { if (context.Entry(entity).State == EntityState.Detached) { dbSet.Attach(entity); } dbSet.Remove(entity); } public void Delete(object id) { TEntity entityToDelete = dbSet.Find(id); Delete(entityToDelete); } public IEnumerator<TEntity> GetEnumerator() { return dbSet.AsEnumerable().GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return dbSet.AsEnumerable().GetEnumerator(); } public Type ElementType { get { return dbSet.AsQueryable().ElementType; } } public Expression Expression { get { return dbSet.AsQueryable().Expression; } } public IQueryProvider Provider { get { return dbSet.AsQueryable().Provider; } } }
The unit of work (abridged since all the getter methods are basically the same):
public class SqlUnitOfWork : IUnitOfWork, IDisposable { private SQLGenericRepository<Application> applications; private SQLGenericRepository<AspNetRole> aspNetRoles; private SQLGenericRepository<AspNetUserClaim> aspNetUserClaims; private SQLGenericRepository<AspNetUserLogin> aspNetUserLogins; private SQLGenericRepository<AspNetUser> aspNetUsers; private SQLGenericRepository<C__MigrationHistory> c__MigrationHistory; private SQLGenericRepository<LuBranch> luBranches; private SQLGenericRepository<LuDegree> luDegrees; private SQLGenericRepository<LuEligibility> luEligibilities; private SQLGenericRepository<LuGrade> luGrades; private SQLGenericRepository<LuSchool> luSchools; private SQLGenericRepository<LuState> luStates; private SQLGenericRepository<LuUnit> luUnits; private SQLGenericRepository<Term> terms; private SQLGenericRepository<UnitPersonnel> unitPersonnels; private BranchUnitRepository branchRepository; private ApplicationTermsRepository termRepository; private EducationAssistanceEntities context = new EducationAssistanceEntities(); public IGenericRepository<Application> Applications { get { if (this.applications == null) { this.applications = new SQLGenericRepository<Application>(context); } return applications; } } public IGenericRepository<AspNetRole> AspNetRoles { get { if (this.aspNetRoles == null) { this.aspNetRoles = new SQLGenericRepository<AspNetRole>(context); } return aspNetRoles; } } ... public void Commit() { context.SaveChanges(); } //disposal code private bool disposed = false; protected virtual void Dispose(bool disposing) { if (!this.disposed) { if (disposing) { context.Dispose(); } } this.disposed = true; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } //other repos public IBranchUnitRepository _branchRepository { get { if (this.branchRepository == null) { this.branchRepository = new BranchUnitRepository(context); } return branchRepository; } } public IApplicationTermRepository _termRepository { get { if (this.termRepository == null) { this.termRepository = new ApplicationTermsRepository(context); } return termRepository; } } }
Now I could drop an IUnitOfWork into the controller, and everything would pretty much just work as is. I did have to make a few minor changes (Add became Insert, Remove became Delete, SaveChanges became Commit) and tweak the way Updates were handled, but for the most part it was a pretty smooth change. New and improved Controller:
public class ApplicationController : BaseController { //private EducationAssistanceEntities db = new EducationAssistanceEntities(); IUnitOfWork db; IPrincipal _user; public ApplicationController() { db = new SqlUnitOfWork(); _user = System.Threading.Thread.CurrentPrincipal; } public ApplicationController(IUnitOfWork _db, IPrincipal user) { db = _db; _user = user; }
The only other big refactor was making EAApplicationModel take an injected dependency, but again since IUnitOfWork looked so much like DbContext, the only thing I had to do was change the constructors. These:
public EAApplicationModel() { //Return new Application Application = new Application(); } public EAApplicationModel(int? id) { //Go out and get their application Application = _ctx.Applications.Find(id); }
Became these:
public EAApplicationModel(int? id) { //Go out and get their application new EAApplicationModel(id, new SqlUnitOfWork(), System.Threading.Thread.CurrentPrincipal); } public EAApplicationModel(int? id, IUnitOfWork ctx, IPrincipal _user) { //Go out and get their application Application = ctx.Applications.Where(a => a.Application_Id == id).SingleOrDefault(); _ctx = ctx; user = _user; } public EAApplicationModel() { //Return new Application new EAApplicationModel(new SqlUnitOfWork(), System.Threading.Thread.CurrentPrincipal); } public EAApplicationModel(IUnitOfWork ctx, IPrincipal _user) { //Return new Application Application = new Application(); _ctx = ctx; user = _user; }
So now that the production code base is pretty much set up, it's time to start setting up the fakes. First up is faking the UnitOfWork and all those repositories to use in memory data...
No comments:
Post a Comment