模拟标识列行为
Mock Identity column behavior
我正在模拟 DbContext
进行单元测试,当您在数据库中保存更改时,您添加的实例会拉取数据库标识列分配的新 ID,有什么方法可以模拟此行为吗? ,我真的不知道从哪里开始。
var acc = new Account {Name = "A New Account"};
_db.Accounts.Add(acc);
_db.SaveChanges();
Assert.IsTrue(acc.Id > 0);
哪里
public class TestDbContext : IEntities
{
public DbSet<Instance> Accounts { get; set; } = new MockDbSet<Accounts>();
}
和
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
namespace ControliApiTests.Data
{
public class MockDbSet<T> : DbSet<T>, IQueryable, IEnumerable<T> where T : class
{
readonly ObservableCollection<T> _data;
readonly IQueryable _queryable;
public MockDbSet()
{
_data = new ObservableCollection<T>();
_queryable = _data.AsQueryable();
}
public virtual T Find(params object[] keyValues)
{
throw new NotImplementedException("Derive from MockDbSet<T> and override Find");
}
public Task<T> FindAsync(CancellationToken cancellationToken, params object[] keyValues)
{
throw new NotImplementedException();
}
public override T Add(T item)
{
_data.Add(item);
return item;
}
public override IEnumerable<T> AddRange(IEnumerable<T> entities)
{
var addRange = entities as T[] ?? entities.ToArray();
foreach (var entity in addRange)
{
_data.Add(entity);
}
return addRange;
}
public override T Remove(T item)
{
_data.Remove(item);
return item;
}
public override T Attach(T item)
{
_data.Add(item);
return item;
}
public override T Create()
{
return Activator.CreateInstance<T>();
}
public override TDerivedEntity Create<TDerivedEntity>()
{
return Activator.CreateInstance<TDerivedEntity>();
}
public override ObservableCollection<T> Local
{
get { return _data; }
}
Type IQueryable.ElementType
{
get { return _queryable.ElementType; }
}
System.Linq.Expressions.Expression IQueryable.Expression
{
get { return _queryable.Expression; }
}
IQueryProvider IQueryable.Provider
{
get { return new AsyncQueryProviderWrapper<T>(_queryable.Provider); }
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _data.GetEnumerator();
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return _data.GetEnumerator();
}
}
internal class AsyncQueryProviderWrapper<T> : IDbAsyncQueryProvider
{
private readonly IQueryProvider _inner;
internal AsyncQueryProviderWrapper(IQueryProvider inner)
{
_inner = inner;
}
public IQueryable CreateQuery(Expression expression)
{
return new AsyncEnumerableQuery<T>(expression);
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new AsyncEnumerableQuery<TElement>(expression);
}
public object Execute(Expression expression)
{
return _inner.Execute(expression);
}
public TResult Execute<TResult>(Expression expression)
{
return _inner.Execute<TResult>(expression);
}
public Task<object> ExecuteAsync(Expression expression, CancellationToken cancellationToken)
{
return Task.FromResult(Execute(expression));
}
public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
{
return Task.FromResult(Execute<TResult>(expression));
}
}
public class AsyncEnumerableQuery<T> : EnumerableQuery<T>, IDbAsyncEnumerable<T>
{
public AsyncEnumerableQuery(IEnumerable<T> enumerable) : base(enumerable)
{
}
public AsyncEnumerableQuery(Expression expression) : base(expression)
{
}
public IDbAsyncEnumerator<T> GetAsyncEnumerator()
{
return new AsyncEnumeratorWrapper<T>(this.AsEnumerable().GetEnumerator());
}
IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator()
{
return GetAsyncEnumerator();
}
}
public class AsyncEnumeratorWrapper<T> : IDbAsyncEnumerator<T>
{
private readonly IEnumerator<T> _inner;
public AsyncEnumeratorWrapper(IEnumerator<T> inner)
{
_inner = inner;
}
public void Dispose()
{
_inner.Dispose();
}
public Task<bool> MoveNextAsync(CancellationToken cancellationToken)
{
return Task.FromResult(_inner.MoveNext());
}
public T Current
{
get { return _inner.Current; }
}
object IDbAsyncEnumerator.Current
{
get { return Current; }
}
}
}
如果你定义
private static int IdentityCounter = 1;
在您的模拟实现中,每添加一个项目就将其递增 1,您将获得一个递增的值,只要应用程序域存在,该值就不会重置。
如果您的测试允许 multi-threaded 添加,请使用 Interlocked.Increment 更新计数器。
请注意,您当前的实现不要求对象具有 ID 属性。如果测试中的所有 类 都有这样的 属性,您可以定义一个接口来使用而不是允许任何 class
.
public interface DbEntity
{
int Id { get; set; }
}
public class MockDbSet<T> : DbSet<T>, IQueryable, IEnumerable<T> where T : DbEntity
有了这个更改,您的 Add 实现可能看起来像
public override T Add(T item)
{
item.Id = IdentityCounter++; // Or use Interlocked.Increment to support multithreading
_data.Add(item);
return item;
}
如果您不想使用接口,可以使用反射和扩展方法来获取和评估 id
var MockSet = new Mock<DbSet<T>>();
MockSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(MockData.AsQueryable().Provider);
MockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(MockData.AsQueryable().Expression);
MockSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(MockData.AsQueryable().ElementType);
MockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => MockData.AsQueryable().GetEnumerator());
MockSet.Setup(m => m.Add(It.IsAny<T>())).Callback<T>(MockData.AddPlus); // here change te method 'Add' for the extension method 'AddPlus'
public static void AddPlus<T>(this List<T> miLista, T item)
{
int nuevoId;
int? id;
try
{
id = (int)item.GetPropValue("id");
}
catch
{
id = null;
}
if (id == 0)
{
if (miLista.Count() > 0)
{
var listaInts = miLista.Select(i => (int)i.GetPropValue("id"));
nuevoId = listaInts.Max(x=>x) + 1;
}
else
nuevoId = 1;
item.SetPropValue("id",nuevoId);
}
miLista.Add(item);
}
我正在模拟 DbContext
进行单元测试,当您在数据库中保存更改时,您添加的实例会拉取数据库标识列分配的新 ID,有什么方法可以模拟此行为吗? ,我真的不知道从哪里开始。
var acc = new Account {Name = "A New Account"};
_db.Accounts.Add(acc);
_db.SaveChanges();
Assert.IsTrue(acc.Id > 0);
哪里
public class TestDbContext : IEntities
{
public DbSet<Instance> Accounts { get; set; } = new MockDbSet<Accounts>();
}
和
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
namespace ControliApiTests.Data
{
public class MockDbSet<T> : DbSet<T>, IQueryable, IEnumerable<T> where T : class
{
readonly ObservableCollection<T> _data;
readonly IQueryable _queryable;
public MockDbSet()
{
_data = new ObservableCollection<T>();
_queryable = _data.AsQueryable();
}
public virtual T Find(params object[] keyValues)
{
throw new NotImplementedException("Derive from MockDbSet<T> and override Find");
}
public Task<T> FindAsync(CancellationToken cancellationToken, params object[] keyValues)
{
throw new NotImplementedException();
}
public override T Add(T item)
{
_data.Add(item);
return item;
}
public override IEnumerable<T> AddRange(IEnumerable<T> entities)
{
var addRange = entities as T[] ?? entities.ToArray();
foreach (var entity in addRange)
{
_data.Add(entity);
}
return addRange;
}
public override T Remove(T item)
{
_data.Remove(item);
return item;
}
public override T Attach(T item)
{
_data.Add(item);
return item;
}
public override T Create()
{
return Activator.CreateInstance<T>();
}
public override TDerivedEntity Create<TDerivedEntity>()
{
return Activator.CreateInstance<TDerivedEntity>();
}
public override ObservableCollection<T> Local
{
get { return _data; }
}
Type IQueryable.ElementType
{
get { return _queryable.ElementType; }
}
System.Linq.Expressions.Expression IQueryable.Expression
{
get { return _queryable.Expression; }
}
IQueryProvider IQueryable.Provider
{
get { return new AsyncQueryProviderWrapper<T>(_queryable.Provider); }
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _data.GetEnumerator();
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return _data.GetEnumerator();
}
}
internal class AsyncQueryProviderWrapper<T> : IDbAsyncQueryProvider
{
private readonly IQueryProvider _inner;
internal AsyncQueryProviderWrapper(IQueryProvider inner)
{
_inner = inner;
}
public IQueryable CreateQuery(Expression expression)
{
return new AsyncEnumerableQuery<T>(expression);
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new AsyncEnumerableQuery<TElement>(expression);
}
public object Execute(Expression expression)
{
return _inner.Execute(expression);
}
public TResult Execute<TResult>(Expression expression)
{
return _inner.Execute<TResult>(expression);
}
public Task<object> ExecuteAsync(Expression expression, CancellationToken cancellationToken)
{
return Task.FromResult(Execute(expression));
}
public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
{
return Task.FromResult(Execute<TResult>(expression));
}
}
public class AsyncEnumerableQuery<T> : EnumerableQuery<T>, IDbAsyncEnumerable<T>
{
public AsyncEnumerableQuery(IEnumerable<T> enumerable) : base(enumerable)
{
}
public AsyncEnumerableQuery(Expression expression) : base(expression)
{
}
public IDbAsyncEnumerator<T> GetAsyncEnumerator()
{
return new AsyncEnumeratorWrapper<T>(this.AsEnumerable().GetEnumerator());
}
IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator()
{
return GetAsyncEnumerator();
}
}
public class AsyncEnumeratorWrapper<T> : IDbAsyncEnumerator<T>
{
private readonly IEnumerator<T> _inner;
public AsyncEnumeratorWrapper(IEnumerator<T> inner)
{
_inner = inner;
}
public void Dispose()
{
_inner.Dispose();
}
public Task<bool> MoveNextAsync(CancellationToken cancellationToken)
{
return Task.FromResult(_inner.MoveNext());
}
public T Current
{
get { return _inner.Current; }
}
object IDbAsyncEnumerator.Current
{
get { return Current; }
}
}
}
如果你定义
private static int IdentityCounter = 1;
在您的模拟实现中,每添加一个项目就将其递增 1,您将获得一个递增的值,只要应用程序域存在,该值就不会重置。
如果您的测试允许 multi-threaded 添加,请使用 Interlocked.Increment 更新计数器。
请注意,您当前的实现不要求对象具有 ID 属性。如果测试中的所有 类 都有这样的 属性,您可以定义一个接口来使用而不是允许任何 class
.
public interface DbEntity
{
int Id { get; set; }
}
public class MockDbSet<T> : DbSet<T>, IQueryable, IEnumerable<T> where T : DbEntity
有了这个更改,您的 Add 实现可能看起来像
public override T Add(T item)
{
item.Id = IdentityCounter++; // Or use Interlocked.Increment to support multithreading
_data.Add(item);
return item;
}
如果您不想使用接口,可以使用反射和扩展方法来获取和评估 id
var MockSet = new Mock<DbSet<T>>();
MockSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(MockData.AsQueryable().Provider);
MockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(MockData.AsQueryable().Expression);
MockSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(MockData.AsQueryable().ElementType);
MockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => MockData.AsQueryable().GetEnumerator());
MockSet.Setup(m => m.Add(It.IsAny<T>())).Callback<T>(MockData.AddPlus); // here change te method 'Add' for the extension method 'AddPlus'
public static void AddPlus<T>(this List<T> miLista, T item)
{
int nuevoId;
int? id;
try
{
id = (int)item.GetPropValue("id");
}
catch
{
id = null;
}
if (id == 0)
{
if (miLista.Count() > 0)
{
var listaInts = miLista.Select(i => (int)i.GetPropValue("id"));
nuevoId = listaInts.Max(x=>x) + 1;
}
else
nuevoId = 1;
item.SetPropValue("id",nuevoId);
}
miLista.Add(item);
}