In my app I have generic repository connected to controller through UnitOfWork. I want to unit test my app. To make this I need to mock db connection. Can you tell me what should do? Mock repo? Mock repo and UnitOfWork? I'll be gratefull for any code snippets/suggestions. Here my Repo:
public class GenericRepository<TEntity> where TEntity : class
{
internal EquipmentEntities context;
internal DbSet<TEntity> dbSet;
public GenericRepository(EquipmentEntities context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
public virtual IEnumerable<TEntity> Get(
List<Expression<Func<TEntity, bool>>> filter,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
int? Page=0,
params Expression<Func<TEntity, object>>[] included)
{
IQueryable<TEntity> query = dbSet;
foreach(var z in included)
{
query=query.Include(z);
}
if (orderBy != null)
{
query = orderBy(query);
query = query.Skip((Page.Value - 1) * 30).Take(30);
}
if (filter != null)
{
foreach (var z in filter)
{
query = query.Where(z);
}
}
return query.ToList();
}
public virtual TEntity GetByID(object id)
{
return dbSet.Find(id);
}
public virtual void Insert(TEntity entity)
{
dbSet.Add(entity);
}
public virtual void Delete(object id)
{
TEntity entityToDelete = dbSet.Find(id);
Delete(entityToDelete);
}
public virtual void Delete(TEntity entityToDelete)
{
if (context.Entry(entityToDelete).State == EntityState.Detached)
{
dbSet.Attach(entityToDelete);
}
dbSet.Remove(entityToDelete);
}
public virtual void Update(TEntity entityToUpdate)
{
dbSet.Attach(entityToUpdate);
context.Entry(entityToUpdate).State = EntityState.Modified;
}
}
and UnitOfWork:
public class UnitOfWork {
private EquipmentEntities context = new EquipmentEntities();
private GenericRepository<Role> RoleRepository;
private GenericRepository<Storage> StorageRepository;
private GenericRepository<Device> DeviceRepository;
private GenericRepository<DeviceInstance> DeviceInstanceRepository;
private GenericRepository<DeviceUsage> DeviceUsageRepository;
private GenericRepository<User> UserRepository;
public GenericRepository<Role> roleRepository
{
get
{
if (this.RoleRepository == null)
{
this.RoleRepository = new GenericRepository<Role>(context);
}
return RoleRepository;
}
}
/*
* redundant code for other controllers
*/
public void Save()
{
context.SaveChanges();
}
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);
}
}
sample controller:
public class UserController : Controller
{
//private EquipmentEntities db = new EquipmentEntities();
private UnitOfWork unitOfWork = new UnitOfWork();
// GET: /User/
public ActionResult Index(string Name, string Surname, int? Page, string submit)
{
List<Expression<Func<User, bool>>> where = new List<Expression<Func<User, bool>>>();
if (!string.IsNullOrEmpty(Name))
{
where.Add(w => w.Name.Contains(Name));
}
if (!string.IsNullOrEmpty(Surname))
{
where.Add(w => w.Surname.Contains(Surname));
}
var users = unitOfWork.userRepository.Get(where, null, Page, u => u.Role);
return View(users);
}
// GET: /User/Details/5
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
User user = unitOfWork.userRepository.GetByID(id.Value);
//User user = db.Users.Find(id);
if (user == null)
{
return HttpNotFound();
}
return View(user);
}
// GET: /User/Create
public ActionResult Create()
{
ViewBag.RoleId = new SelectList(unitOfWork.roleRepository.Get(null), "Id", "RoleName");
return View();
}
// POST: /User/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include="Id,EmployeeNo,Name,Surname,ContactInfo,RoleId")] User user)
{
if (ModelState.IsValid)
{
unitOfWork.userRepository.Insert(user);
unitOfWork.Save();
return RedirectToAction("Index");
}
ViewBag.RoleId = new SelectList(unitOfWork.roleRepository.Get(null), "Id", "RoleName", user.RoleId);
return View(user);
}
// GET: /User/Edit/5
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
User user = unitOfWork.userRepository.GetByID(id.Value);
if (user == null)
{
return HttpNotFound();
}
ViewBag.RoleId = new SelectList(unitOfWork.roleRepository.Get(null), "Id", "RoleName", user.RoleId);
return View(user);
}
// POST: /User/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="Id,EmployeeNo,Name,Surname,ContactInfo,RoleId")] User user)
{
if (ModelState.IsValid)
{
unitOfWork.userRepository.Update(user);
unitOfWork.Save();
return RedirectToAction("Index");
}
ViewBag.RoleId = new SelectList(unitOfWork.roleRepository.Get(null), "Id", "RoleName", user.RoleId);
return View(user);
}
// GET: /User/Delete/5
public ActionResult Delete(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
User user = unitOfWork.userRepository.GetByID(id.Value);
if (user == null)
{
return HttpNotFound();
}
if (unitOfWork.deviceUsageRepository.Get(null).Where(w => w.UserId == id) != null)
{
ViewBag.Error = 1;
ModelState.AddModelError("", "Nie można kasować uyztkownika z przypisanymi urządzeniami");
}
else
{
ViewBag.Error = 0;
}
return View(user);
}
// POST: /User/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
User user = unitOfWork.userRepository.GetByID(id);
unitOfWork.deviceUsageRepository.Delete(user);
unitOfWork.Save();
return RedirectToAction("Index");
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
unitOfWork.Save();
}
base.Dispose(disposing);
}
}
Mocking is a way to encapsulate your unit tests. If you want to test a service method you are not interested if the repository is working. For this you will write repository tests. Therefore you mock the repository call and tell which result should be returned to test your method in all possible situation.
Yes, you should test your repository layer. Although the majority of these tests fall into a different classification of tests. I usually refer to them as integration tests to distinguish them from my unit tests.
Moq is a mocking framework built to facilitate the testing of components with dependencies. As shown earlier, dealing with dependencies could be cumbersome because it requires the creation of test doubles like fakes. Moq makes the creation of fakes redundant by using dynamically generated types.
Unfortunately your GenericRepository<T>
is tightly coupled with your context, and your UnitOfWork implementation if tightly coupled with your repositories. This makes it impossible to mock it.
You have to introduce loosely coupling:
IRepository<T>
, and implement this with your GenericRepository<T>
classIUnitOfWork
and implement this with your UnitOfWork
classIUnitOfWork
interface only refers to IRepository<T>
, not to GenericRepository<T>
IUnitOfWork
instead of a UnitOfWork.IRepositoryFactory
(with a corresponding implementation), which would allow you to create a specific repository on demand. The factory would have a generic Create
method to create a generic repository. This factory could then be injected in your unit-of-work implementation.Now you can mock every part of you unit of work and/or repositories.
I've removed the repository-factory in the text above, and the code below. The reason for this, is that when I tried to create pseudo-code I had a bit of trouble to pass the context to the generic-repository, since the repository-factory didn't know this object. And, since the unit-of-work and the generic-repository are both tightly coupled anyway (since they share the context-object), I've come up with the following solution:
public interface IRepository<TEntity> where TEntity: class {
// Your methods
}
public class GenericRepository<TEntity> : IRepository<TEntity> where TEntity : class {
public GenericRepository<TEntity>(EquipmentEntities context) {
// Your constructor
}
// Your implementation
}
public interface IUnitOfWork : IDisposable {
IRepository<Role> RoleRepository { get; }
IRepository<Storage> StorageRepository { get; }
// etc
void Save();
}
public class UnitOfWork : IUnitOfWork {
public UnitOfWork () {
this.context = new EquipmentEntities ();
}
private EquipmentEntities context = null;
private IRepository<Role> roleRepository;
public IRepository<Role> RoleRepository {
get {
if (this.roleRepository == null) {
this.roleRepository = new GenericRepository<Role>(context);
}
return this.roleRepository;
}
}
// etc... other repositories
// etc... your implementation for Save and Dispose
}
As noted, you have high cohesion between your classes.
The preferable method is to break this cohesion (introducing isolation) by using interfaces. However, you can also use Microsoft's faking framework to create shims. Shims allow you to divert the behavior of methods and properties of an object to create simulations of concrete types.
Using shims to isolate your application from other assemblies for unit testing
Shim types are one of two technologies that the Microsoft Fakes Framework uses to let you easily isolate components under test from the environment. Shims divert calls to specific methods to code that you write as part of your test. Many methods return different results dependent on external conditions, but a shim is under the control of your test and can return consistent results at every call. This makes your tests much easier to write.
Use shims to isolate your code from assemblies that are not part of your solution. To isolate components of your solution from each other, we recommend that you use stubs.
http://msdn.microsoft.com/en-us/library/hh549176.aspx
As of writing, you have already accepted an answer. However, the faking framework is darkest wizard magic and should be explored. Using shims will give you a good eye for where an interface is required.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With