Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock Repository/Unit Of Work

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);
    }
}
like image 220
szpic Avatar asked Feb 18 '14 07:02

szpic


People also ask

What is mock repository?

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.

Should you unit test repository?

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.

What is mock MOQ?

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.


2 Answers

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:

  • Add an interface IRepository<T>, and implement this with your GenericRepository<T> class
  • Add in interface IUnitOfWork and implement this with your UnitOfWork class
  • The IUnitOfWork interface only refers to IRepository<T>, not to GenericRepository<T>
  • Update your controller constructors to expect an IUnitOfWork instead of a UnitOfWork.
  • Best would be to inject the repositories in you unit of work, but that would mean a lot of constructor-parameters, and you would have an instance of it already while you might not be using it. The solution to this in my mind would be a 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.

Update

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
}
like image 133
Maarten Avatar answered Sep 28 '22 00:09

Maarten


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.

like image 20
Gusdor Avatar answered Sep 28 '22 01:09

Gusdor