我在我的项目中实现了 Repository 和 Unit Of Work,并且使用了相同的架构,这里是参考链接 https://github.com/ahmadnegm/Publishers.EF.CodeFirst.Repository.UnitOfWork.Demo。所以,在我的项目中有三层(DAL、BLL和UI),我打算在 BLL 的单元测试中使用 Mocking,但是对于这个架构如何使用它感到非常困惑,因为我有一些模型在 BLL 中使用,这就是我需要测试的内容。
注:我阅读了一些话题,比如这个,这个和这个,但实际上我没有得到符合我的情况,所以,如果您可以指导我如何在我的单元测试中使用 Mocking,那将是很好的。
代码示例:DAL
IUnitOfWork:
注:我阅读了一些话题,比如这个,这个和这个,但实际上我没有得到符合我的情况,所以,如果您可以指导我如何在我的单元测试中使用 Mocking,那将是很好的。
代码示例:DAL
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:
在BLL中,我为DAL中的每个实体创建了一个模型,用于与UI层通信。使用AutoMapper编写了一个扩展方法,可以将实体转换为模型,反之亦然。对于每个模型,我都有一个类,其中包含我需要实现该实体的所有逻辑。以下是我需要使用Mock测试的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.");
}
}
}