问题描述
在我的应用程序中,我具有通过UnitOfWork连接到控制器的通用存储库.我想对我的应用进行单元测试.为此,我需要模拟数据库连接.你能告诉我该怎么办吗?模拟回购?模拟回购和UnitOfWork?我将不胜感激任何代码段/建议.这是我的仓库:
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;
}
}
和工作单位:
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);
}
}
示例控制器:
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);
}
}
推荐答案
不幸的是,您的GenericRepository<T>
与您的上下文紧密相关,而您的UnitOfWork实现与您的存储库紧密相关.这使得无法模拟它.
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.
您必须引入松散耦合:
- 添加接口
IRepository<T>
,并使用您的GenericRepository<T>
类实现 - 添加接口
IUnitOfWork
并使用UnitOfWork
类实现 -
IUnitOfWork
界面仅引用IRepository<T>
,不引用引用GenericRepository<T>
- 更新您的控制器构造函数以期望使用
IUnitOfWork
而不是UnitOfWork.
- Add an interface
IRepository<T>
, and implement this with yourGenericRepository<T>
class - Add in interface
IUnitOfWork
and implement this with yourUnitOfWork
class - The
IUnitOfWork
interface only refers toIRepository<T>
, not toGenericRepository<T>
- Update your controller constructors to expect an
IUnitOfWork
instead of a UnitOfWork.
现在,您可以模拟工作单元和/或存储库的每个部分.
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
}
这篇关于如何模拟存储库/工作单元的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!