我在项目中实现了Repository和Unit Of Work,我正在使用相同的架构here。所以,我的项目中有3层( DAL , BLL 和 UI ),我将使用 Mocking 在BLL的单元测试中,但是我对这个架构使用它感到困惑,因为我有BLL中使用的模型,这是我需要测试的。
注意:我已经阅读了一些主题,例如this,this和this,但实际上我没有得到与我匹配的案例,所以如果你可以指导我完成这项工作会很棒,如何在我的单元测试中使用Mocking。
IUnitOfWork :
public interface IUnitOfWork : IDisposable
{
IRepository<T> GetRepository<T>() where T : Entity;
int Save();
int SaveInDbTransaction(IsolationLevel isolationLevel = IsolationLevel.ReadCommitted);
string ErrorMessage { get; }
}
IRepository :
public interface IRepository<TEntity> : IDisposable
{
IQueryable<TEntity> All(Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "");
IEnumerable<TEntity> GetWithRawSql(string query, params object[] parameters);
IQueryable<TEntity> Filter(Expression<Func<TEntity, bool>> predicate);
IQueryable<TEntity> Filter<TKey>(Expression<Func<TEntity, bool>> filter, out int total, int index = 0, int size = 50);
bool Contains(Expression<Func<TEntity, bool>> predicate);
TEntity Find(params object[] keys);
TEntity Find(Expression<Func<TEntity, bool>> predicate);
void Create(TEntity entity);
void Delete(object entityId);
void Delete(TEntity entity);
void Delete(Expression<Func<TEntity, bool>> predicate);
void Update(TEntity entity);
int Count { get; }
}
的UnitOfWork :
public class UnitOfWork<TContext> : IUnitOfWork where TContext : DbContext
{
public string ErrorMessage { get; private set; }
private readonly TContext _context;
private bool _disposed;
private Dictionary<string, object> _repositories;
public UnitOfWork()
{
ErrorMessage = null;
_context = Activator.CreateInstance<TContext>();
_repositories = new Dictionary<string, object>();
}
public UnitOfWork(TContext context)
{
_context = context;
ErrorMessage = null;
}
public IRepository<TSet> GetRepository<TSet>() where TSet : Entity
{
if (_repositories == null)
{
_repositories = new Dictionary<string, object>();
}
if (_repositories.ContainsKey(typeof(TSet).Name))
{
return _repositories[typeof(TSet).Name] as IRepository<TSet>;
}
var repositoryInstance = new Repository<TSet, TContext>(_context);
_repositories.Add(typeof(TSet).Name, repositoryInstance);
return repositoryInstance;
}
public int Save()
{
try
{
#region Handling auditing
var modifiedEntries = _context.ChangeTracker.Entries()
.Where(x => x.Entity is IAuditableEntity
&& (x.State == EntityState.Added ||
x.State == EntityState.Modified));
foreach (var entry in modifiedEntries)
{
var entity = entry.Entity as IAuditableEntity;
if (entity != null)
{
var identityName = Thread.CurrentPrincipal.Identity.Name;
var now = DateTime.UtcNow;
if (entry.State == EntityState.Added)
{
entity.CreatedBy = identityName;
entity.Created = now;
}
else
{
_context.Entry(entity).Property(x => x.CreatedBy).IsModified = false;
_context.Entry(entity).Property(x => x.Created).IsModified = false;
}
entity.ModifiedBy = identityName;
entity.Modified = now;
}
}
#endregion
var affectedRows = _context.SaveChanges();
return affectedRows;
}
catch (DbEntityValidationException dbEx)
{
foreach (var validationError in dbEx.EntityValidationErrors.SelectMany(
validationErrors => validationErrors.ValidationErrors))
{
ErrorMessage += $"Property: {validationError.PropertyName} Error: {validationError.ErrorMessage}" +
Environment.NewLine;
}
throw new Exception(ErrorMessage, dbEx);
}
catch (Exception exception)
{
ErrorMessage = exception.Message;
throw new Exception(ErrorMessage, exception);
}
}
public int SaveInDbTransaction(IsolationLevel isolationLevel = IsolationLevel.ReadCommitted)
{
DbContextTransaction transaction = null;
try
{
transaction = _context.Database.BeginTransaction(IsolationLevel.ReadCommitted);
using (transaction)
{
#region Handling auditing
var modifiedEntries = _context.ChangeTracker.Entries()
.Where(x => x.Entity is IAuditableEntity
&& (x.State == EntityState.Added ||
x.State == EntityState.Modified));
foreach (var entry in modifiedEntries)
{
var entity = entry.Entity as IAuditableEntity;
if (entity != null)
{
var identityName = Thread.CurrentPrincipal.Identity.Name;
var now = DateTime.UtcNow;
if (entry.State == EntityState.Added)
{
entity.CreatedBy = identityName;
entity.Created = now;
}
else
{
_context.Entry(entity).Property(x => x.CreatedBy).IsModified = false;
_context.Entry(entity).Property(x => x.Created).IsModified = false;
}
entity.ModifiedBy = identityName;
entity.Modified = now;
}
}
#endregion
var affectedRows = _context.SaveChanges();
transaction.Commit();
return affectedRows;
}
}
catch (DbEntityValidationException dbEx)
{
foreach (var validationError in dbEx.EntityValidationErrors.SelectMany(
validationErrors => validationErrors.ValidationErrors))
{
ErrorMessage += $"Property: {validationError.PropertyName} Error: {validationError.ErrorMessage}" +
Environment.NewLine;
}
transaction?.Rollback();
throw new Exception(ErrorMessage, dbEx);
}
catch (Exception exception)
{
ErrorMessage = exception.Message;
transaction?.Rollback();
throw new Exception(ErrorMessage, exception);
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_context.Dispose();
}
}
_disposed = true;
}
}
存储库:
public class Repository<TEntity, TContext> : IRepository<TEntity>
where TEntity : Entity
where TContext : DbContext
{
private readonly TContext _context;
protected DbSet<TEntity> DbSet => _context.Set<TEntity>();
public Repository(TContext session)
{
_context = session;
}
public void Dispose()
{
_context?.Dispose();
}
public IQueryable<TEntity> All(Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "")
{
//return DbSet.AsQueryable();
var query = DbSet.AsQueryable();
if (filter != null)
{
query = query.Where(filter);
}
query = includeProperties.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries)
.Aggregate(query, (current, includeProperty) => current.Include(includeProperty));
return orderBy?.Invoke(query).AsQueryable() ?? query.AsQueryable();
}
public IEnumerable<TEntity> GetWithRawSql(string query, params object[] parameters)
{
return DbSet.SqlQuery(query, parameters).ToList();
}
public IQueryable<TEntity> Filter(Expression<Func<TEntity, bool>> predicate)
{
return DbSet.Where(predicate).AsQueryable();
}
public IQueryable<TEntity> Filter<TKey>(Expression<Func<TEntity, bool>> predicate, out int total, int index = 0,
int size = 50)
{
var result = DbSet.Where(predicate);
total = result.Count();
return result.Skip(index).Take(size);
}
public bool Contains(Expression<Func<TEntity, bool>> predicate)
{
return DbSet.Count(predicate) > 0;
}
public TEntity Find(params object[] keys)
{
return DbSet.Find(keys);
}
public TEntity Find(Expression<Func<TEntity, bool>> predicate)
{
return DbSet.FirstOrDefault(predicate);
}
public void Create(TEntity entity)
{
DbSet.Add(entity);
}
public void Delete(object entityId)
{
var entity = DbSet.Find(entityId);
if (entity != null)
{
DbSet.Remove(entity);
}
}
public void Delete(TEntity entity)
{
DbSet.Remove(entity);
}
public void Delete(Expression<Func<TEntity, bool>> predicate)
{
var objects = Filter(predicate);
foreach (var obj in objects)
DbSet.Remove(obj);
}
public void Update(TEntity entity)
{
var entry = _context.Entry(entity);
DbSet.Attach(entity);
entry.State = EntityState.Modified;
}
public int Count => DbSet.Count();
}
在BLL中,我为DAL中的每个实体建立了一个与UI层进行通信的模型,并且有一个扩展方法,使用 AutoMapper 从实体转换为模型,反之亦然,我有一个类对于每个模型,其中包含我需要使用该实体实现的所有逻辑,这里是一个BLL类的示例,我需要使用模拟来测试它:
public class ClientManager
{
public int Add(ClientModel model)
{
var entity = model.ToEntity();
using (var uow = new UnitOfWork<SubscriptionContext>())
{
if (model.IsValid())
{
var entityRepository = uow.GetRepository<Data.Entities.Client>();
entityRepository.Create(entity);
var affected = uow.Save();
if (affected < 1)
{
throw new Exception(uow.ErrorMessage);
}
Logger.Log(Logger.LogLevel.Information, this.GetType().FullName, MethodBase.GetCurrentMethod(), "Adding new entity: " + entity.Id, null, Thread.CurrentPrincipal.Identity.Name);
return affected;
}
else
{
throw new Exception("Model is not valid.");
}
}
}
public int Update(ClientModel model)
{
var entity = model.ToEntity();
using (var uow = new UnitOfWork<SubscriptionContext>())
{
if (model.IsValid())
{
var entityRepository = uow.GetRepository<Data.Entities.Client>();
entityRepository.Update(entity);
var affected = uow.Save();
if (affected < 1)
{
throw new Exception(uow.ErrorMessage);
}
Logger.Log(Logger.LogLevel.Information, this.GetType().FullName, MethodBase.GetCurrentMethod(), "Updating existing entity: " + entity.Id, null, Thread.CurrentPrincipal.Identity.Name);
return affected;
}
else
{
throw new Exception("Model is not valid.");
}
}
}
public int Delete(int entityId)
{
using (var uow = new UnitOfWork<SubscriptionContext>())
{
if (entityId > 0)
{
var entityRepository = uow.GetRepository<Data.Entities.Client>();
entityRepository.Delete(entityId);
var affected = uow.Save();
if (affected < 1)
{
throw new Exception(uow.ErrorMessage);
}
Logger.Log(Logger.LogLevel.Information, this.GetType().FullName, MethodBase.GetCurrentMethod(), "Removing existing entity: " + entityId, null, Thread.CurrentPrincipal.Identity.Name);
return affected;
}
else
{
throw new Exception("There is no data to delete at the current position.");
}
}
}
public ClientModel Find(int entityId)
{
using (var uow = new UnitOfWork<SubscriptionContext>())
{
if (entityId > 0)
{
var entityRepository = uow.GetRepository<Data.Entities.Client>();
var entity = entityRepository.Find(entityId);
if(entity != null) {
return entity.ToModel();
}
}
throw new Exception("There is no data to delete at the current position.");
}
}
}
答案 0 :(得分:2)
您想要模拟,但您似乎没有使用任何依赖注入。相反,您只需在任何需要的地方创建自己的UnitOfWork<SubscriptionContext>
实现。
我建议您查看依赖注入,并实际注册UnitOfWorkFactory
以插入ClientManager
。
您的代码看起来像这样:
public class ClientManager
{
private readonly IUnitOfWorkFactory UowFactory;
public ClientManager(IUnitOfWorkFactory<SubscriptionContext> uowFactory)
{
UowFactory = uowFactory;
}
public int Add(ClientModel model)
{
var entity = model.ToEntity();
using (var uow = uowFactory.GetUoW())
{
// dowork
}
}
}
您可以阅读依赖注入(例如使用unity)和工厂模式onlin,例如here
现在,在您的单元测试中,您可以简单地使用自己的IUnitOfWorkFactory
实现,在该实现中返回模拟UoW,如下所示:
var UowMock = new Mock<IUnitOfWork<SubscriptionContext>();
var UowFactoryMock = new Mock<IUowFactory>();
UowFactoryMock.Stub(f => f.GetUoW()).Returns(UowMock);
var clientManager = new ClientManager(UowFactoryMock);
// Test whatever you want in your clientManager!
当然,您可能必须设置工作单元以在调用方法时返回预期值。如何做到这完全取决于您的测试框架。